for (const std::string &title: titles_playing_nearby) std::cout << title << std::endl;
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:
begin()
method.
const_iterator
type,
with prefix operators *
and ++
.
end()
method.
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.