Boost logo

Boost :

Subject: Re: [boost] quince: queries in C++ expressions
From: Roland Bock (rbock_at_[hidden])
Date: 2014-07-17 08:48:09


On 2014-07-17 13:11, Michael Shepanski wrote:
> On 17/07/2014 5:09 PM, Roland Bock wrote:
>> It is really fascinating to see that many ideas are almost identical.
>> sqlpp11 also has a grouping some columns and give the group a name. It
>> is called multi_column, can be accessed by name just like columns and is
>> of course of great value in joins :-)
>
> The difference, of course, is that in quince we don't speak of
> "columns" so much. :)
:-)
>
> Every query produces values of its "value type", which can be any type
> in this taxonomy:
> http://quince-lib.com/mapped_data_types.html#mapped_data_types.definition_taxonomy
> . It is common for a query's value type to be the same as a table's
> value type, but this is not the case for queries that use join() or
> select().
Shouldn't collections be mentioned on that page, too?
 
>
>
>>> All of these points apply the same to normal joins or self-joins. The
>>> only difference with self-joins is the use of table_alias objects in
>>> lieu of table objects.
>> Got it :-)
>>
>> Does quince support adding columns at runtime, btw? Say, I have a table
>> that contains some meta data and a blob. I always want to select the
>> meta data, but whether or not to select the blob is decided at runtime.
>
> Sure. Quince's select() is analogous to supplying an SQL "select
> list". (I say "analogous", not identical, because here again we work
> at the level of C++ types, without regard to whether they are
> single-column or multi-column.)
>
> So it's:
>
> struct something {
> metadata meta; // metadata is some mapped type defined elsewhere
> vector<uint8_t> blob;
> string other;
> };
> QUINCE_MAP_CLASS(something, (meta)(blob)(other))
>
> extern table<something> somethings;
>
> // retrieve metadata only:
> for (const metadata &m: somethings.select(somethings->meta))
> // ...
>
> // retrieve all three parts:
> for (const something &s: somethings)
> // ...
>
> // retrieve metadata and blob only:
> for (const std::tuple<meta, vector<uint8_t>> &pair:
> somethings.select(somethings->meta, somethings->blob))
> // ...
>
> In the last example, we could use a collector class if we don't like
> tuples.

Hmm. I guess my question was unclear or I misunderstood you answer.

I was thinking something like

auto query = somethings.select(somethings->meta)
if (userWantsBlob)
   query.add_to_select_list(somethings-blob);
for(const auto& row : query)
{
   if (userWantsBlob)
   {
      //somehow access blob here
   }
}

We have some code in our company that has several of these dynamic
fields. Multiplying the code is not really an option then.

>
>> Since you have watched the "selected template toffees" video, you have
>> seen all the tools already. Here's a walk through:
>>
>> * You have an expression tree and a context type. The latter is
>> provided by the database
>> * sqlpp11 provides a template function called serialize, which takes
>> an generic expression tree and a generic context (basically any
>> output stream).
>> * sqlpp11 also provides "partial specializations" of these functions
>> via a serializer template class. The partial specializations are
>> available for all building blocks of the EDSL (e.g. tables,
>> columns,
>> select, where, arithmetic expressions, ...) and the generic
>> context.
>> You can see those at the bottom of each header file for such a
>> building block, e.g.
>>
>> https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/group_by.h
>> * If you need special behavior, you add a specialization for the
>> respective building block and your own context type.
>> * If your special behavior is that you do not support that feature,
>> you just say so in a static_assert which fires when the serializer
>> is instantiate
>>
>>
>> Here's an example from
>> https://github.com/rbock/sqlpp11-connector-sqlite3/blob/master/include/sqlpp11/sqlite3/serializer.h
>>
>> (sqlite does not support full outer join):
>>
>> template<typename Lhs, typename Rhs, typename On>
>> struct serializer_t<sqlite3::serializer_t, join_t<outer_join_t, Lhs,
>> Rhs, On>>
>> {
>> using T = join_t<outer_join_t, Lhs, Rhs, On>;
>> static void _(const T& t, sqlite3::serializer_t& context)
>> {
>> static_assert(::sqlpp::wrong_t<outer_join_t, Lhs, Rhs, On>::value, "No
>> support for outer join");
>> }
>> };
>
> Okay, I think I understand. When you have a complex expression tree
> that includes, say, a full join somewhere deep inside, then that
> affects the static type of the expression tree as a whole. Therefore,
> when you ask a connector to serialize the tree, the compiler can see
> the incompatibility and fail. Yes?
That's correct for queries with a static structure. For dynamic parts
(like the optional blob in the example above), the compatibility would
be tested when the dynamic part is added to the query.
>
> If so, then it confirms what I suspected (albeit for different
> reasons): I can't make quince behave this way -- or at least the price
> of doing so would be very high. A complex quince::query object may
> have a full join buried somewhere inside, but it doesn't affect the
> object's static type. (It only becomes apparent when quince tries to
> generate the SQL, so it traverses the tree, calling virtual methods,
> etc.)
If you knew the database type while constructing the query, you could do
those tests during construction. It probably wouldn't even be that hard
with a similar mechanism as used in sqlpp11 (easy for me to say, still
not knowing really much about your code).

Best,

Roland


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk