Boost logo

Boost :

Subject: Re: [boost] sqlpp11, 3rd iteration
From: Roland Bock (rbock_at_[hidden])
Date: 2014-08-20 04:50:41

On 2014-08-20 00:34, Adam Wulkiewicz wrote:
> Roland Bock wrote:
>> On 2014-08-19 18:56, Adam Wulkiewicz wrote:
>>> Ok AFAIU a struct like _member_t should define some convenient member
>>> variable for the user and must define operator() for the library.
>>> But the rest could be automatically generated, couldn't it?
>>> Why not just pass a list of templates of classes adapted to MemberType
>>> concept (defined operator()) into the table/column/etc.?
>>> I'm thinking about something like the code below. I don't know exactly
>>> what's required so this is just an example of a technique rather than
>>> a solution ready-to-use in sqlpp.
>> The idea is good. For the columns, you will have to add a few more
>> parameters, e.g. the value_type (mandatory), can_be_null,
>> must_not_insert, must_not_update, null_is_trivial, trivial_is_null,
>> maybe as per your suggestion a default value or a function for producing
>> it. That default stuff might be tough in such a design.
> The additional traits would be a list of variadic template parameters.
> So if this list contained only one type, e.g. sqlpp::default_traits
> the default traits could be generated e.g. by specializing
> sqlpp::make_traits<>.
> Since it's impossible to define a default argument of a template
> parameters pack (another missing language feature?) it could be
> "simulated" with something like:
> template <template <typename> class Name,
> typename Trait0 = default_traits,
> typename... Traits
> struct column
> {
> using traits = make_traits<Trait0, Traits...>;
> };
> Or all traits could be passed as one list type like MPL sequence or
> someting like that as you wrote below.
>> But thats manageable. And yes, the code would be shorter, although not
>> that much, I suspect. The only problem I have with it is that now the
>> column types are going to be about a hundred characters long. And users
>> are going to operate on columns all the time. So error message have to
>> be short.
> Do you have in mind the code of the library or user's code?

User's code:

  * You have the member template which must be defined outside.
  * You have the get_name method which should be defined outside the
    member template since I have no instance of that template where I
    need the name (you still cant use a string literal as template
    parameter directly, right? Like table_t<"sample">?)
  * You need to group the member template and the get_name method since
    they are always used in combination
  * You need a struct or class to hold the default value or function

And if you don't want to have all this flying around as individual
pieces with individual names, then you will group it into a class. And
you're back to where you started.

> I expect that the user's code, even not using defaults, would be a lot
> shorter.
> But the most important is that the definition of a table would
> probably be more clear, in one place, etc.
> Or am I wrong?

I think you're wrong, although I'd love to be wrong about that :-)

Based on my thoughts above you'd end up with

    struct Alpha
      struct _name_t
        static constexpr const char* _get_name() { return "alpha"; }
        template<typename T>
        struct _member_t
            T alpha;
            T& operator()() { return alpha; }
            const T& operator()() const { return alpha; }
     struct _trivial_t
        int64_t get_trivial_value() { return 42; }

struct alpha: public column_t<MyTable, Alpha,
sqlpp::make_traits<sqlpp::integral, ...>>;

(I need to be able to combine name and trivial value freely, for
instance when using an alias of a column, thats why those have to be

I seem to be ending up with exactly the same number of lines in the user

Technically, I /could/ do without the name_t and move the get_name
function into the member template code, but that would also mean
inheriting multiple versions of the get_name method into tables and rows.

>> I would thus add a struct which inherits from the column template
>> instance for each column, e..g.
>> struct alpha: public column<tab_member, alpha_member, sqlpp::integral,
>> sqlpp::tag::must_not_insert, ...> {};
> With variadic templates the construction of traits out of this would
> be straightforward.
> An alternative would be to take additional parameters/traits list as
> the 3rd parameter as you wrote below.
> Btw, why a column must be aware about a Table?

For three reasons at least:

 1. representation: in most statement types, more than one table can be
    involved, e.g. when using some kind of join. To avoid name clashes,
    columns are represented as tablename.columnname when being serialized
 2. consistency checking: sqlpp11 performs a lot of chekcs at compile
    time that your statements are consistent, for instance, it detects
    if you are selecting columns from tables which are not mentioned in
    the from clause. It therefore has to know which tables the selected
    columns belong to.
 3. determining can_be_null for result fields: if you are using any of
    the outer joins, then selected columns of those outer tables can be
    null. In order to determine this at compile time, again the column
    has to be associated with its table.

And this association has to be done not only for those pre-defined
tables and their columns, it also has to work with sub-selects which are
used as tables, of course :-)

> Can a table also have some traits specified?

Not today, but that will probably change soon. read-only would be a very
good trait for a table, for instance.

> I'm asking because then there would be 2 lists that should be passed -
> Members and Traits.
>> I tried something similar a while back but failed, which is mainly due
>> to lack of perseverance, I guess.
>> Right now, I am happy with the current design because it is quite easy
>> to change things, like introducing that default value or a function for
>> handling attempts to read NULL.
> Sure, I'm not saying that you should change the design. I'm just
> sharing my thoughts.

And I really appreciate it :-)

>> If you want to put everything into that one list of template parameters,
>> it is much tougher, IMO. I mean how would you add a function for
>> handling access to NULL value? You would need another class, I think.
>> And you would have to group those tags into a tuple or type_set, because
>> otherwise it would be ugly to add another optional parameter...
> I'm guessing that the function or ... could be passed as yet another
> trait like:
> struct alpha: public column<tab_member, alpha_member,
> sqlpp::trivial_value<some_generator> >
> If not passed, a default trivial value would be used.
> The best would be to somehow pass a static value in compile-time but
> only integral types could be handled this way. The reference to the
> global external variable of non-integral type could also be passed as
> a template parameter but still it would have to be defined somewhere
> so it wouldn't be convenient.
> So some_generator could be a type of default-constructible function
> object or a pointer to function, etc.
> Or do someone knows some trick that could be used here?

Well, anonymous in-place class definitions would help to keep the
relevant information in one place. Another missing language feature, I
think. Something like

struct alpha : public column_t<
   struct {
      static constexpr const char* _get_name() { return "alpha"; }
      template<typename T>
            T alpha;
            T& operator()() { return alpha; }
            const T& operator()() const { return alpha; }
   struct { int64_t _get_trivial() const { return 42;}},
> {};

But that's still not much shorter :-(

I believe that the key is the name stuff. If we could use names in the
same way as types and values, for instance as template parameters, this
would be much easier, both in user code and in the library code.



Boost list run by bdawes at, gregod at, cpdaniel at, john at