We have seen how to construct our query in a single C++ expression that covered nine lines. Now let's try it in smaller steps.
First we define a couple of C++ functions to build the mathematical parts of the query:
// Build a server-side expression that squares a float: // exprn_mapper<float> square(const exprn_mapper<float> &arg) { return arg * arg; } // Build a server-side expression that computes the square of the // distance between two points. // template<typename Point0, typename Point1> exprn_mapper<float> distance_squared(const Point0 &point0, const Point1 &point1) { return square(point0.x - point1.x) + square(point0.y - point1.y); }
Then we build the query in stages:
const query<cinema> local_cinemas = cinemas .where(distance_squared(cinemas->location, my_place) <= square(max_radius)); const query<std::tuple<cinema, screen>> local_cinemas_and_their_screens = local_cinemas .inner_join( screens, screens->cinema_id == local_cinemas->id ); const query<std::tuple<std::tuple<cinema, screen>, movie>> local_cinemas_and_their_screens_and_current_movies = local_cinemas_and_their_screens .inner_join( movies, movies->id == local_cinemas_and_their_screens->get<1>().current_movie_id ); const query<std::string> titles_playing_nearby = local_cinemas_and_their_screens_and_current_movies .select(local_cinemas_and_their_screens_and_current_movies->get<1>().title) .distinct();
That spells out what was implicit in our first attempt, but now that
I see it I don't entirely like it. The two “joined” queries
(locals_cinemas_and_their_screens
and local_cinemas_and_their_screens_and_current_movies
)
both produce tuples containing largely unused information. It's probably
not a performance issue, because those queries themselves are never executed.
(The only query that is executed is the final one, titles_playing_nearby
,
and it has enough information to let the DBMS make sensible optimizations.)
Nevertheless the tuples complicate the C++ source code, and for that
reason I prefer the following (with the definition of local_cinemas
unchanged):
const query<screen> local_screens = local_cinemas .inner_join(screens, screens->cinema_id == local_cinemas->id) .select(*screens); const query<movie> local_movies = local_screens .inner_join(movies, movies->id == local_screens->current_movie_id) .select(*movies); const query<std::string> titles_playing_nearby = local_movies .select(local_movies->title) .distinct();
The combination of inner_join()
and select()
, in the definitions of local_screens
and local_movies
,
serves to “jump across” from one query (or table) to another.
Quince has a convenience function, jump()
, whose job is to abbreviate that pattern.
So my last revision is:
const query<screen> local_screens = local_cinemas .jump(screens, screens->cinema_id == local_cinemas->id); const query<movie> local_movies = local_screens .jump(movies, movies->id == local_screens->current_movie_id); const query<std::string> titles_playing_nearby = local_movies .select(local_movies->title) .distinct();