SqliteModernCpp / sqlite_modern_cpp

The C++14 wrapper around sqlite library
MIT License
904 stars 156 forks source link

Support for named and indexed parameters #170

Closed zauguin closed 6 years ago

zauguin commented 6 years ago

This is a UDL based attempt to support binding named parameters: For example

db << "INSERT INTO t VALUES(:firstname, :lastname, :birthday);"
   << ":birthday"_sqlparam("2000-01-01")
   << ":firstname"_sqlparam("John")
   << ":lastname"_sqlparam("Doe");

I am not really happ with the name _sqlparam. Does somebody have a better idea? Another question is how to handle mixed positional and named parameters: Currently _sqlparam paameters are ignored in the normal order, so for example

db << "SELECT :abc, ?;" << ":abc"_sqlparam(1) << 2;

would bind both parameters, 1 and 2, to :abc so the result would be 2, NULL. This is not perfect but I do not know about a better way and using both named and ? parameters together is advised against in the SQLite manual anyway, so fixing this might not be the top priority.

zauguin commented 6 years ago

@aminroosta The motivation are prepared statements where the parameters are not bound immediately after creating the statement. For example I have a function which accepts a prepared statement as a parameter. During the execution the stament is executed multiple times with different values. The ? syntax forces the caller to use the parameters inthe statement in a order implicitly defined by the callee. Also changing the implementation such that the parameter values are known and bound in a different order becomes a API breaking change.

The caller can create a work-around by using the ?1, ?2, ... syntax but at this point the number already acts like a name and no longer has a real meaning. So I think it is useful to allow descriptive names too.

This also gives more flexibility to the implementation of the callee.

Also the SQLite documentation says about ? parameters: "the use of this parameter format is discouraged". The reasons do not apply for typical uses of the library, because counting the question marks is not necessary, but especially for cases where SQL statements are shared between code in different languages some support for named types might be a good idea.

This becomes even more useful in combination with #169. On the other hand these changes but can be seen as a too big change in the fundamental design of the current API.

aminroosta commented 6 years ago

@zauguin You make a really good point, I also agree :-)

Let's talk about the interface, I will propose to use template character packs template<char ...c> (or template<char const *>) and have an interface like this:

db << "INSERT INTO t VALUES(:firstname, :lastname, :birthday);"
   <<  named_parameter<":birthday">("2000-01-01")
   << named_parameter<":firstname">("John")
   << named_parameter<":lastname">("Doe");

I have not thought about the details of implementation but given we have class template argument deduction it should be possible to implement in C++17.

What do you think?

zauguin commented 6 years ago

@aminroosta Literal strings are not valid non-type template arguments, so AFAICT syntax like named_parameter<":birthday"> can not be implemented. There is a proposal to change this for C++20, P0424, but in C++17 we have to live without it.

We could create a function with two regular parameters:

db << "INSERT INTO t VALUES(:firstname, :lastname, :birthday);"
   << named_parameter(":birthday", "2000-01-01")
   << named_parameter(":firstname", "John")
   << named_parameter(":lastname", "Doe");
aminroosta commented 6 years ago

@zauguin Thanks for the proposal link, yea makes sense.

Between the two syntaxes ":birthday"_named_parameter("2000-01-01") and named_parameter(":birthday", "2000-01-01"), i slightly prefer the function version ... but both look good to me, i leave it to you to choose.

zauguin commented 6 years ago

Let's use the function then, this also allows non-literals. Then I would use indexed_parameter for the numbered version.