PrevUpHomeNext

Executing the Query

    for (const std::string &title: titles_playing_nearby)
        std::cout << title << std::endl;

Remarks

This is a C++11 for loop. In case you haven't seen one of those before, it's as though we had written the following (and feel free to enter this code instead, if you prefer it):

    for (query<std::string>::const_iterator i = titles_playing_nearby.begin();
         i != titles_playing_nearby.end();
         ++i) {
        const std::string &title = *i;
        std::cout << title << std::endl;
    }

Either way, what the code demonstrates, implicitly in one case and explicitly in the other, is that a query has:

The begin() method does most of the work. It translates the query into SQL, maps any accompanying data into columns, and sends all of that to the DBMS, which starts to execute it. begin() also creates a const_iterator object, which is the data structure that receives rows of query output as they arrive. Then begin() waits for the first row (or, for PostgreSQL, the first batch or rows); it converts the first row to the query's value type (std::string in our case); and it saves the converted item inside the const_iterator. Finally it returns the const_iterator. [6]

const_iterator's operator * simply returns a const reference to the converted item, which the const_iterator already holds.

const_iterator's operator ++ gets the next row of output (either by finding it in the buffer or by waiting for the the DBMS to produce some more), converts that row to the value type, and saves the converted item inside the const_iterator, replacing the previously saved value.

Eventually a call to ++ will find that there is no more output. Then it will put the const_iterator into an "end" state. (If there had been no output at at all, begin() would have created it in that state.) This is the state where comparisons to end() return true -- or you can test for it directly by calling i.is_end().

The end() method itself does very little. It returns a special const_iterator, with the property that i==end() reduces to i.is_end() for any const_iterator i.

Of course the reason quince defines end() is for interoperability with the C++11 for loop, and with other code that is geared towards STL containers. For the same reason, quince defines an iterator type (a typedef of const_iterator), and within the const_iterator class you will find such things as a postfix operator ++ (identical to the prefix ++, since they both return void), and types pointer and reference, among others.

The prize for all this STL fancy dress is that our iterators earn the title of input iterators, which carries certain privileges. E.g. any standards-compliant STL implementation must allow:

    const std::vector<std::string> all_of_them(titles_playing_nearby.begin(), titles_playing_nearby.end());

which populates a std::vector by running our query and collecting all the results. (Don't expect to std::sort() a query however: that function requires random access iterators, not mere input iterators.)

Calling begin() is the most general way to execute a query, but it doesn't suit every situation. Sometimes you want to retrieve a single record without all the buffering apparatus. Sometimes you want to execute a command that is not a query, and does not produce even a single record. As we shall see, there is more than one way to execute SQL. You don't have to pay for machinery you don't need.



[6] All of these steps rely heavily on the backend library, but our application code isn't mentioning that. Quince knows which tables the query was built from; it knows which database object we passed to the table constructor each time, and it knows the backend-specific type of that database object; so we don't need to tell it again.


PrevUpHomeNext