$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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