sqlpp11: SQL for C++
Hi, over the last four or five years I developed several SQL libraries for C++. With C++11 I was finally able to create an SQL library that meets my own expectations and requirements. It is being used in production, I recently put it on github, and I would really like to hear from you guys whether this is something that could be interesting for you personally or for boost? https://github.com/rbock/sqlpp11 https://github.com/rbock/sqlpp11/wiki sqlpp11 is a template library representing an embedded domain specific language (EDSL) that allows you to * define types representing tables and columns, * construct type safe queries checked at compile time for syntax errors, type errors, name errors and even some semantic errors, * interpret results by iterating over query-specific structs with appropriately named and typed members. This results in several benefits, e.g. * the library user operates comfortably on structs and functions, * the compiler reports many kinds of errors long before the code enters unit testing or production, * the library hides the gory details of string construction for queries and interpreting string based results returned by select calls. I.e. you don't need to use strings in query construction where you wouldn't use them in SQL and there is no need to use positional arguments or to parse strings when obtaining fields from a result row (the latter being true unless you do not know which columns to select at compile time). The library supports both static and dynamic queries. The former offers greater benefit in terms of type and consistency checking. The latter makes it easier to construct queries on the flight. Specific traits of databases (e.g. unsupported or non-standard features) are known at compile time as well. This way, the compiler can tell the developer at compile time if a query is not accepted by the database (e.g. if a feature is missing). And the library can form the query in the correct manner, for instance if the engine uses concat instead of operator|| to concatenate strings. Two Examples: ============= Static Select: -------------- // selecting zero or more results, iterating over the results for (const auto& row : db.run( select(foo.name, foo.hasFun) .from(foo) .where(foo.id > 17 and foo.name.like("%bar%")))) { if (row.name.is_null()) std::cerr << "name will convert to empty string" << std::endl; std::string name = row.name; // text fields are implicitly convertible to string bool hasFun = hasFun; // bool fields are implicitly convertible to bool } Dynamic Select: ---------------- auto s = dynamic_select(db, tab.foo).dynamic_columns().from(tab); if (userWantsBar) s.add_column(tab.bar); for(const auto& row : run(s)) { std::cerr << "row.foo: " << row.foo; if (userWantsBar) std::cerr << "row.bar" << row.at("bar"); std::cerr << std::endl; }; Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-) Regards, Roland
On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results. Here is one of SOCI's introductory examples: // ------------------------ int id = 17; string name; int salary; sql << "select name, salary from persons where id = " << id, into(name), into(salary); // ------------------------ In sqlpp11 this would read something like this // --------------------- auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17)); if (!result.empty()) { const auto row = result.front(); std::string name = row.name; int salary = row.salary; } // --------------------- Notice that there are no strings being used in the sqlpp11 code at all. The compiler can see and check the syntax of your query. If you mistype anything, or if you compare apples and oranges (e.g. a text column and an int), or if you forget the 'from' before the 'where' for instance, it will let you know with compile errors. Also, it is much harder to mix up stuff in the results. Imagine a slightly larger list of columns being selected. With SOCI you need to keep the "select a, b, c, d" in sync with the into(a), into(b), into(c), into(d) sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you. sqlpp11 assumes that you know your tables at compile time. Thus you can declare types representing tables and columns with appropriate names and types. You can then construct SQL queries and analyze the results with the full armory of syntax and type checking that C++ and template meta programming have to offer. Regards, Roland
On 9 November 2013 22:58, Roland Bock
On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results.
Yes, that's correct. In fact, SQL commands play an important role as part of DBMS access abstraction. SQL is a part of SOCI interface, by design.
sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you.
sqlpp11 assumes that you know your tables at compile time. Thus you can declare types representing tables and columns with appropriate names and types. You can then construct SQL queries and analyze the results with the full armory of syntax and type checking that C++ and template meta programming have to offer.
Thank you for this clarification. I think sqlpp11 is an extremely interesting exercise. I have been considering to add non-string layer to SOCI directly based on your idea, soon after I saw it some time ago in your initial experiments. Best regards, -- Mateusz Loskot, http://mateusz.loskot.net
On 2013-11-10 00:12, Mateusz Loskot wrote:
On 9 November 2013 22:58, Roland Bock
wrote: On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results. Yes, that's correct.
In fact, SQL commands play an important role as part of DBMS access abstraction. SQL is a part of SOCI interface, by design.
sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you.
sqlpp11 assumes that you know your tables at compile time. Thus you can declare types representing tables and columns with appropriate names and types. You can then construct SQL queries and analyze the results with the full armory of syntax and type checking that C++ and template meta programming have to offer. Thank you for this clarification.
I think sqlpp11 is an extremely interesting exercise. :-)
I have been considering to add non-string layer to SOCI directly based One option would be to write a sqlpp11 connector library for SOCI, similar to the existing ones for using MySQL/MariaDb and Sqlite3.
on your idea, soon after I saw it some time ago in your initial experiments.
Best regards, You're referring to the discussion in 2010? http://comments.gmane.org/gmane.comp.lib.boost.devel/208623
Cool, I wasn't aware of the impact :-) sqlpp11 is much more mature, than what I had back then. Among other things * auto allows for a much leaner perceived API * table definitions are also much simpler. I am still using a code generator for those (DDL->C++, which I'll add to the repository soon), but the types are quite comprehensible even for a casual user, I guess * compile times are better * sqlpp11 "understands" sub-selects, which were a nightmare in all my previous attempts. Sub-selects can be used very naturally now, i.e. as selected values, as input for functions like exists(), as operands in where-conditions and as pseudo-tables in from(). The documentation on that is still pretty thin, I guess, but there are usage hints here and there: https://github.com/rbock/sqlpp11/wiki/Select#sub-select https://github.com/rbock/sqlpp11/wiki/Select#aliased-sub-select * you can add columns to your select at runtime now, if required * I would not call the library code simple, but it is certainly much easier to understand and to extend than any of the earlier versions Cheers, Roland
Hi Roland, On 2013-11-09 23:58, Roland Bock wrote:
On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results. Here is one of SOCI's introductory examples:
// ------------------------ int id = 17; string name; int salary; sql << "select name, salary from persons where id = " << id, into(name), into(salary); // ------------------------
In sqlpp11 this would read something like this
// --------------------- auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17)); if (!result.empty()) { const auto row = result.front(); std::string name = row.name; int salary = row.salary; } // ---------------------
Notice that there are no strings being used in the sqlpp11 code at all. The compiler can see and check the syntax of your query. If you mistype anything, or if you compare apples and oranges (e.g. a text column and an int), or if you forget the 'from' before the 'where' for instance, it will let you know with compile errors.
Also, it is much harder to mix up stuff in the results. Imagine a slightly larger list of columns being selected. With SOCI you need to keep the "select a, b, c, d" in sync with the into(a), into(b), into(c), into(d)
sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you. There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface. For example instead of this:
auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17)); Your library could have the following interface: auto result = db.run(QUERY("select name,salary from persons where id=17")); The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above. The documentation of Metaparse is here: http://abel.web.elte.hu/mpllibs/metaparse/ You can find examples here: https://github.com/sabel83/mpllibs/tree/master/libs/metaparse/example Regards, Ábel
On 11/11/13 13:35, Abel Sinkovics wrote:
Hi Roland,
On 2013-11-09 23:58, Roland Bock wrote:
On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results. Here is one of SOCI's introductory examples:
// ------------------------ int id = 17; string name; int salary; sql << "select name, salary from persons where id = " << id, into(name), into(salary); // ------------------------
In sqlpp11 this would read something like this
// --------------------- auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17)); if (!result.empty()) { const auto row = result.front(); std::string name = row.name; int salary = row.salary; } // ---------------------
Notice that there are no strings being used in the sqlpp11 code at all. The compiler can see and check the syntax of your query. If you mistype anything, or if you compare apples and oranges (e.g. a text column and an int), or if you forget the 'from' before the 'where' for instance, it will let you know with compile errors.
Also, it is much harder to mix up stuff in the results. Imagine a slightly larger list of columns being selected. With SOCI you need to keep the "select a, b, c, d" in sync with the into(a), into(b), into(c), into(d)
sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you. There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface. For example instead of this:
auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17));
Your library could have the following interface:
auto result = db.run(QUERY("select name,salary from persons where id=17"));
The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above.
Guessing from code here: https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/select.h#L574 The sql string written to oss would be something like the argument to your QUERY function. IOW, IIUC, there's no need for parsing a string to build the right classes. OTOH, the string passed to the actual database (via the db on select.h#L574) would have to be parsed, I assume, by dbms, which might issue some error message or return some error code if the sql string were not right. I think Roland's code almost guarantee's the sql string would be correct. Is that about right Roland? [snip]
On 2013-11-11 21:14, Larry Evans wrote:
On 11/11/13 13:35, Abel Sinkovics wrote:
Hi Roland,
On 2013-11-09 23:58, Roland Bock wrote:
On 2013-11-09 23:03, Michael Marcin wrote:
On 11/9/2013 4:03 PM, Roland Bock wrote:
Please let me know your questions/thoughts/suggestions/rants. Contributions welcome, of course :-)
Could you compare your library with SOCI?
I consider SOCI to be a string and position based approach, meaning that you (as a library user) have to use strings to construct your queries and positions to extract results. Here is one of SOCI's introductory examples:
// ------------------------ int id = 17; string name; int salary; sql << "select name, salary from persons where id = " << id, into(name), into(salary); // ------------------------
In sqlpp11 this would read something like this
// --------------------- auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17)); if (!result.empty()) { const auto row = result.front(); std::string name = row.name; int salary = row.salary; } // ---------------------
Notice that there are no strings being used in the sqlpp11 code at all. The compiler can see and check the syntax of your query. If you mistype anything, or if you compare apples and oranges (e.g. a text column and an int), or if you forget the 'from' before the 'where' for instance, it will let you know with compile errors.
Also, it is much harder to mix up stuff in the results. Imagine a slightly larger list of columns being selected. With SOCI you need to keep the "select a, b, c, d" in sync with the into(a), into(b), into(c), into(d)
sqlpp11 takes care of this responsibility for you and gives you rows with appropriately named and typed member variables. It is much harder to use those the wrong way without the compiler yelling at you. There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface. For example instead of this:
auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17));
Your library could have the following interface:
auto result = db.run(QUERY("select name,salary from persons where id=17"));
Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples like the one above.
The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above.
Guessing from code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/select.h#L574
The sql string written to oss would be something like the argument to your QUERY function. IOW, IIUC, there's no need for parsing a string to build the right classes.
OTOH, the string passed to the actual database (via the db on select.h#L574) would have to be parsed, I assume, by dbms, which might issue some error message or return some error code if the sql string were not right. I think Roland's code almost guarantee's the sql string would be correct.
Is that about right Roland?
That is correct, Larry, nicely guessed from the code, indeed :-) The query is constructed via functions and objects to build an expression which /can/ be evaluated as a string which is then being sent to the database. This is also the current use case. But there have been several ideas presented in this thread what else could be done (evaluating XML or JSON or incoming streams). In those cases, it might be better to transform the query into another representation. Regarding the correctness of the string: That's the goal, yes. Best regards, Roland
Hi,
On 2013-11-11 21:14, Larry Evans wrote:
On 11/11/13 13:35, Abel Sinkovics wrote:
There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface. For example instead of this:
auto result = db.run(select(persons.name, persons.salary).from(persons).where(persons.id == 17));
Your library could have the following interface:
auto result = db.run(QUERY("select name,salary from persons where id=17"));
Metaparse requires const char[N] arguments, right?
No. It uses the preprocessor to generate something like metaparse::string<'s','e','l','e','c','t',' ','n',',...> The downside is, that metaparse has a configurable maximum string length and increasing it also increases compile times. I am not sure, weather it would be possible to have reasonable compile times with SQL strings as they exist in real world applications. Christof -- okunah gmbh i.L. Software nach Maß Zugspitzstr. 211 www.okunah.de 86165 Augsburg cd@okunah.de Registergericht Augsburg Geschäftsführer Augsburg HRB 21896 Christof Donat UStID: DE 248 815 055
On Mon, Nov 11, 2013 at 9:59 PM, Christof Donat
No. It uses the preprocessor to generate something like
metaparse::string<'s','e','l','e','c','t',' ','n',',...>
The downside is, that metaparse has a configurable maximum string length and increasing it also increases compile times. I am not sure, weather it would be possible to have reasonable compile times with SQL strings as they exist in real world applications.
doesn't it still makes impossible to have variables in the request?
On Mon, Nov 11, 2013 at 7:02 PM, Klaim - Joël Lamotte
On Mon, Nov 11, 2013 at 9:59 PM, Christof Donat
wrote: No. It uses the preprocessor to generate something like
metaparse::string<'s','e','l','e','c','t',' ','n',',...>
doesn't it still makes impossible to have variables in the request?
In theory, you could bind using placeholders like :a, :b. I.e.: string<'name', '=:a ', 'and ', 'age=', ':age'> // name=:a and age=:age These would be bound using the database backend. Not sure if all DBs support this, though. Regards, Rodrigo Madera
Hi, Am Montag, 11. November 2013, 22:02:50 schrieb Klaim - Joël Lamotte:
On Mon, Nov 11, 2013 at 9:59 PM, Christof Donat
wrote: No. It uses the preprocessor to generate something like
metaparse::string<'s','e','l','e','c','t',' ','n',',...>
The downside is, that metaparse has a configurable maximum string length and increasing it also increases compile times. I am not sure, weather it would be possible to have reasonable compile times with SQL strings as they exist in real world applications.
doesn't it still makes impossible to have variables in the request?
Yes and no. That could be solved with positional parameters like boost::format: db.run(QUERY("select name,salary from persons where id=%1" % 17)); Christof -- okunah gmbh i.L. Software nach Maß Zugspitzstr. 211 www.okunah.de 86165 Augsburg cd@okunah.de Registergericht Augsburg Geschäftsführer Augsburg HRB 21896 Christof Donat UStID: DE 248 815 055
Hi, On 2013-11-11 21:59, Christof Donat wrote:
The downside is, that metaparse has a configurable maximum string length and increasing it also increases compile times. I am not sure, weather it would be possible to have reasonable compile times with SQL strings as they exist in real world applications. If it turns out to be too slow, a constexpr/template metaprogram combined approach might help. For example constexpr functions could be used to tokenise the input. There is an example showing that constexpr and metaprogram parsers can be combined:
https://github.com/sabel83/mpllibs/tree/master/libs/metaparse/example/conste... It parses "a*b*a*", where the "a*" parts are parsed with metaprograms while the "b*" parts are parsed with constexpr. Regards, Ábel
Hi, On 2013-11-11 21:31, Roland Bock wrote:
Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples like the one above. Yes, it takes char[N] arguments, however, you can use a boost::format-like syntax (as mentioned by Christof) or a printf-like, type checked one.
The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above. Guessing from code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/select.h#L574
The sql string written to oss would be something like the argument to your QUERY function. IOW, IIUC, there's no need for parsing a string to build the right classes.
OTOH, the string passed to the actual database (via the db on select.h#L574) would have to be parsed, I assume, by dbms, which might issue some error message or return some error code if the sql string were not right. I think Roland's code almost guarantee's the sql string would be correct.
Is that about right Roland? That is correct, Larry, nicely guessed from the code, indeed :-)
The query is constructed via functions and objects to build an expression which /can/ be evaluated as a string which is then being sent to the database. This is also the current use case. But there have been several ideas presented in this thread what else could be done (evaluating XML or JSON or incoming streams). In those cases, it might be better to transform the query into another representation.
Regarding the correctness of the string: That's the goal, yes.
If you don't want to transform the string, just validate it (and maybe do some variable substitution) you can approach it in a similar way the type-checked printf does it: it parses the string, does the validation at compile-time and then uses the original string at runtime. Code of it: https://github.com/sabel83/mpllibs/tree/master/mpllibs/safe_printf Example using it: https://github.com/sabel83/mpllibs/blob/master/libs/safe_printf/example/safe... (here check the C++11 one at the bottom) Regards, Ábel
On 2013-11-11 22:41, Abel Sinkovics wrote:
Hi,
On 2013-11-11 21:31, Roland Bock wrote:
Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples like the one above. Yes, it takes char[N] arguments, however, you can use a boost::format-like syntax (as mentioned by Christof) or a printf-like, type checked one.
The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above. Guessing from code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/select.h#L574
The sql string written to oss would be something like the argument to your QUERY function. IOW, IIUC, there's no need for parsing a string to build the right classes.
OTOH, the string passed to the actual database (via the db on select.h#L574) would have to be parsed, I assume, by dbms, which might issue some error message or return some error code if the sql string were not right. I think Roland's code almost guarantee's the sql string would be correct.
Is that about right Roland? That is correct, Larry, nicely guessed from the code, indeed :-)
The query is constructed via functions and objects to build an expression which /can/ be evaluated as a string which is then being sent to the database. This is also the current use case. But there have been several ideas presented in this thread what else could be done (evaluating XML or JSON or incoming streams). In those cases, it might be better to transform the query into another representation.
Regarding the correctness of the string: That's the goal, yes.
If you don't want to transform the string, just validate it (and maybe do some variable substitution) you can approach it in a similar way the type-checked printf does it: it parses the string, does the validation at compile-time and then uses the original string at runtime.
Code of it: https://github.com/sabel83/mpllibs/tree/master/mpllibs/safe_printf
Example using it: https://github.com/sabel83/mpllibs/blob/master/libs/safe_printf/example/safe... (here check the C++11 one at the bottom)
I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it. But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries. Best regards, Roland
I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries. You can make it possible for the user to provide a textual representation of the SQL query in the SQL language. His SQL queries are
Hi, On 2013-11-11 23:16, Roland Bock wrote: then validated at compile-time. If you want to move away from SQL and build your own DSL for writing SQL-like queries (and then generate the SQL queries from that), it won't help. Regards, Ábel
Le 11/11/13 23:16, Roland Bock a écrit :
Hi,
On 2013-11-11 21:31, Roland Bock wrote:
Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples like the one above. Yes, it takes char[N] arguments, however, you can use a boost::format-like syntax (as mentioned by Christof) or a printf-like, type checked one.
The string could be parsed by a template metaprogram and the right classes could be built out of it. It could provide all the static guarantees you have described above. Guessing from code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/select.h#L574
The sql string written to oss would be something like the argument to your QUERY function. IOW, IIUC, there's no need for parsing a string to build the right classes.
OTOH, the string passed to the actual database (via the db on select.h#L574) would have to be parsed, I assume, by dbms, which might issue some error message or return some error code if the sql string were not right. I think Roland's code almost guarantee's the sql string would be correct.
Is that about right Roland? That is correct, Larry, nicely guessed from the code, indeed :-)
The query is constructed via functions and objects to build an expression which /can/ be evaluated as a string which is then being sent to the database. This is also the current use case. But there have been several ideas presented in this thread what else could be done (evaluating XML or JSON or incoming streams). In those cases, it might be better to transform the query into another representation.
Regarding the correctness of the string: That's the goal, yes. If you don't want to transform the string, just validate it (and maybe do some variable substitution) you can approach it in a similar way the type-checked printf does it: it parses the string, does the validation at compile-time and then uses the original string at runtime.
Code of it: https://github.com/sabel83/mpllibs/tree/master/mpllibs/safe_printf
Example using it: https://github.com/sabel83/mpllibs/blob/master/libs/safe_printf/example/safe... (here check the C++11 one at the bottom) I see use cases for printf and regex for instance, where the user
On 2013-11-11 22:41, Abel Sinkovics wrote: provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries.
Hi, I think that what others are saying is that as your interface is a SQL on, maybe a textual interfaces is even closer to the SQL one ;-) Thus this concrete example |for (const auto& row : db.run(select(foo.name, foo.hasFun) .from(foo) .where(foo.id > 17 and foo.name.like("%bar%")))) | |could be rewritten with something like| || | | |for (const auto& row : db.run<"*select* name, hasFun *from* foo *where ( *id > 17 *and* name *like* "%bar%" )">()) // This need to take care of escaping '"' or a way to quote |||"%bar%"| :( |Clearly this interface can be learn quicker by a SQL developer and can be checked statically using something like Metaparser or the techniques used by Metaparser. Whether this is a good idea depends on * the performances at compile time and :( * the kind of errors the user would have :( * there is something that can be done with the first syntax that can not be done with the ct-string syntax or that can be easier to do. I can understand that you don't want to go this path as it it s complex and could take a lot of time to stabilize it. Anyway, if you can show some examples that you think can not be done parsing a ct-string, this could reinforce your current interface. Note that I like your EDSL interface, but a SQL EDSL interface text based could complement quite nicely the yours. Both approaches could be provided either by your library or by a library on top of yours. HTH, Vicente
On 2013-11-11 23:53, Vicente J. Botet Escriba wrote:
Le 11/11/13 23:16, Roland Bock a écrit :
On 2013-11-11 22:41, Abel Sinkovics wrote:
[snip] I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries.
Hi,
I think that what others are saying is that as your interface is a SQL on, maybe a textual interfaces is even closer to the SQL one ;-) Sure, since SQL is text based :-) Thus this concrete example
|for (const auto& row : db.run(select(foo.name, foo.hasFun) .from(foo) .where(foo.id > 17 and foo.name.like("%bar%"))))
|
|could be rewritten with something like| ||
| | |for (const auto& row : db.run<"*select* name, hasFun *from* foo *where ( *id > 17 *and* name *like* "%bar%" )">()) //
This need to take care of escaping '"' or a way to quote |||"%bar%"| :(
|Clearly this interface can be learn quicker by a SQL developer and can be checked statically using something like Metaparser or the techniques used by Metaparser. Whether this is a good idea depends on * the performances at compile time and :( * the kind of errors the user would have :( * there is something that can be done with the first syntax that can not be done with the ct-string syntax or that can be easier to do.
From the top of my head I can think of the following things that I would consider extremely hard to do with ct string parsing:
_Typos and similar errors:_ ct-parsing can certainly be trained to parse a string, even with placeholders. But what would be the result for the following string? "select nam, hasVun, from bar where name > 17 and id like '%bar%'" Can it detect errors here? sqlpp11 on the other hand gets this: select(foo.nam, foo.hasVun).from(bar).where(foo.name > 17 and foo.id.like("%bar%")); The current sqlpp11 will detect 4 errors here at compile time: Two typos in the selected columns, two times comparing apples and oranges. Starting with one of the next few iterations it will even detect that you used the wrong table. _Result types:_ For static queries, based on the names and types of the selected columns you can access the fields of the result row as appropriately named members with appropriate types, in this case: std::string = row.name; // OK bool hasFun = row.hasFun; // OK bool wrong = row.name; // compile error Could that be achieved with ct string parsing of the query? I wouldn't know where to begin. But I am certainly not an expert in this area. _Dynamic queries:_ sqlpp11 allows you to build queries dynamically way beyond substituting parameters. Selected columns, required tables, where conditions etc can be added at runtime. For example the user could query table Person and have option to add related information from table Job to the result set. With strings? Well you could construct it like "select person.* " + (userWantsJobInfo ? ", job.title" : "") + " from person " + (userWantsJobInfo ? ", job" + " where person.active = 1 " + (userWantsJobInfo ? " and job.id = person.jobId" : ""); I hope I got that right. Can a ct string parser handle it in a useful way? I honestly don't know, but I doubt it. With sqlpp11, on the other hand, it is easy: auto s = dynamic_select(db, all_of(person)).from(person).dynamic_where(person.active == true); if (userWantsJobInfo) { s.add_columns(job.title); s.add_from(job); s.add_where(job.id == person.jobId); } sqlpp11 will simply build the query in the correct way.
I can understand that you don't want to go this path as it it s complex and could take a lot of time to stabilize it. Anyway, if you can show some examples that you think can not be done parsing a ct-string, this could reinforce your current interface.
Note that I like your EDSL interface, but a SQL EDSL interface text based could complement quite nicely the yours. Both approaches could be provided either by your library or by a library on top of yours.
Thanks for your input and summary. I don't think I would go the road of compile time string parsing for SQL, because personally I don't think it is worth the effort. But that should not stop anybody, of course. I could be totally wrong. And as you said, it might be a nice complement to the EDSL approach. Best regards, Roland
Le 12/11/13 09:13, Roland Bock a écrit :
On 2013-11-11 23:53, Vicente J. Botet Escriba wrote:
Le 11/11/13 23:16, Roland Bock a écrit :
On 2013-11-11 22:41, Abel Sinkovics wrote: [snip] I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries.
Hi,
I think that what others are saying is that as your interface is a SQL on, maybe a textual interfaces is even closer to the SQL one ;-) Sure, since SQL is text based :-) Thus this concrete example
|for (const auto& row : db.run(select(foo.name, foo.hasFun) .from(foo) .where(foo.id > 17 and foo.name.like("%bar%"))))
|
|could be rewritten with something like| ||
| | |for (const auto& row : db.run<"*select* name, hasFun *from* foo *where ( *id > 17 *and* name *like* "%bar%" )">()) //
This need to take care of escaping '"' or a way to quote |||"%bar%"| :(
|Clearly this interface can be learn quicker by a SQL developer and can be checked statically using something like Metaparser or the techniques used by Metaparser. Whether this is a good idea depends on * the performances at compile time and :( * the kind of errors the user would have :( * there is something that can be done with the first syntax that can not be done with the ct-string syntax or that can be easier to do. From the top of my head I can think of the following things that I would consider extremely hard to do with ct string parsing:
_Typos and similar errors:_ ct-parsing can certainly be trained to parse a string, even with placeholders. But what would be the result for the following string? "select nam, hasVun, from bar where name > 17 and id like '%bar%'" Can it detect errors here?
sqlpp11 on the other hand gets this: select(foo.nam, foo.hasVun).from(bar).where(foo.name > 17 and foo.id.like("%bar%")); The current sqlpp11 will detect 4 errors here at compile time: Two typos in the selected columns, two times comparing apples and oranges. Starting with one of the next few iterations it will even detect that you used the wrong table.
_Result types:_ For static queries, based on the names and types of the selected columns you can access the fields of the result row as appropriately named members with appropriate types, in this case:
std::string = row.name; // OK bool hasFun = row.hasFun; // OK bool wrong = row.name; // compile error
Could that be achieved with ct string parsing of the query? I wouldn't know where to begin. But I am certainly not an expert in this area.
_Dynamic queries:_ sqlpp11 allows you to build queries dynamically way beyond substituting parameters. Selected columns, required tables, where conditions etc can be added at runtime. For example the user could query table Person and have option to add related information from table Job to the result set.
With strings? Well you could construct it like
"select person.* " + (userWantsJobInfo ? ", job.title" : "") + " from person " + (userWantsJobInfo ? ", job" + " where person.active = 1 " + (userWantsJobInfo ? " and job.id = person.jobId" : ""); This could not be a ct_string if userWantsJobInfo is not constexpr. But each of the parts could be a ct-string (Se below) I hope I got that right. Can a ct string parser handle it in a useful way? I honestly don't know, but I doubt it.
With sqlpp11, on the other hand, it is easy: auto s = dynamic_select(db, all_of(person)).from(person).dynamic_where(person.active == true); if (userWantsJobInfo) { s.add_columns(job.title); s.add_from(job); s.add_where(job.id == person.jobId); } What about something like
auto s = db.dynamic_query<"select person.* from person where person.active">(); if (userWantsJobInfo) { s.combine<"select job.title from job where job.id == person.jobId">(); } Here db.dynamic_query<>() would return the same type as your dynamic_select(db, ), but the analysis of "select person.* from person where person.active" can be done statically. Then s.combine<> would combine the preceding query s, so it knows that there is a person table, with the result of "select job.title from job where job.id == person.jobId". Best, Vicente
On 2013-11-12 19:14, Vicente J. Botet Escriba wrote:
Le 12/11/13 09:13, Roland Bock a écrit :
On 2013-11-11 23:53, Vicente J. Botet Escriba wrote:
Le 11/11/13 23:16, Roland Bock a écrit :
On 2013-11-11 22:41, Abel Sinkovics wrote: [snip] I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries.
Hi,
I think that what others are saying is that as your interface is a SQL on, maybe a textual interfaces is even closer to the SQL one ;-) Sure, since SQL is text based :-) Thus this concrete example
|for (const auto& row : db.run(select(foo.name, foo.hasFun) .from(foo) .where(foo.id > 17 and foo.name.like("%bar%"))))
|
|could be rewritten with something like| ||
| | |for (const auto& row : db.run<"*select* name, hasFun *from* foo *where ( *id > 17 *and* name *like* "%bar%" )">()) //
This need to take care of escaping '"' or a way to quote |||"%bar%"| :(
|Clearly this interface can be learn quicker by a SQL developer and can be checked statically using something like Metaparser or the techniques used by Metaparser. Whether this is a good idea depends on * the performances at compile time and :( * the kind of errors the user would have :( * there is something that can be done with the first syntax that can not be done with the ct-string syntax or that can be easier to do. From the top of my head I can think of the following things that I would consider extremely hard to do with ct string parsing:
_Typos and similar errors:_ ct-parsing can certainly be trained to parse a string, even with placeholders. But what would be the result for the following string? "select nam, hasVun, from bar where name > 17 and id like '%bar%'" Can it detect errors here?
sqlpp11 on the other hand gets this: select(foo.nam, foo.hasVun).from(bar).where(foo.name > 17 and foo.id.like("%bar%")); The current sqlpp11 will detect 4 errors here at compile time: Two typos in the selected columns, two times comparing apples and oranges. Starting with one of the next few iterations it will even detect that you used the wrong table.
_Result types:_ For static queries, based on the names and types of the selected columns you can access the fields of the result row as appropriately named members with appropriate types, in this case:
std::string = row.name; // OK bool hasFun = row.hasFun; // OK bool wrong = row.name; // compile error
Could that be achieved with ct string parsing of the query? I wouldn't know where to begin. But I am certainly not an expert in this area.
_Dynamic queries:_ sqlpp11 allows you to build queries dynamically way beyond substituting parameters. Selected columns, required tables, where conditions etc can be added at runtime. For example the user could query table Person and have option to add related information from table Job to the result set.
With strings? Well you could construct it like
"select person.* " + (userWantsJobInfo ? ", job.title" : "") + " from person " + (userWantsJobInfo ? ", job" + " where person.active = 1 " + (userWantsJobInfo ? " and job.id = person.jobId" : ""); This could not be a ct_string if userWantsJobInfo is not constexpr. But each of the parts could be a ct-string (Se below) I hope I got that right. Can a ct string parser handle it in a useful way? I honestly don't know, but I doubt it.
With sqlpp11, on the other hand, it is easy: auto s = dynamic_select(db, all_of(person)).from(person).dynamic_where(person.active == true); if (userWantsJobInfo) { s.add_columns(job.title); s.add_from(job); s.add_where(job.id == person.jobId); } What about something like
auto s = db.dynamic_query<"select person.* from person where person.active">(); if (userWantsJobInfo) { s.combine<"select job.title from job where job.id == person.jobId">(); }
Here db.dynamic_query<>() would return the same type as your dynamic_select(db, ), but the analysis of "select person.* from person where person.active" can be done statically. Then s.combine<> would combine the preceding query s, so it knows that there is a person table, with the result of "select job.title from job where job.id == person.jobId".
Best, Vicente Sure, not impossible, but as stated before, I won't go there :-)
Best regards, Roland
Hi, Am Montag, 11. November 2013, 23:16:06 schrieb Roland Bock:
I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it?
When you have SQL with its original syntax in your source code, you can copy/paste between your source code and your database tool. That way you can develop your queries iteratively. Compile time string parsing then adds compile time syntax checks, type safety and a intuitive interface to access the columns in the result set by using your library.
The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries.
The huge strength of it is to me, that it provides all that syntax and type checking and the intuitive access to the result sets. Its weakness is, that I have to translate my queries from SQL syntax to another similar, but not equal syntax when interactively working with my SQL tools. That could be resolved by adding compile time string parsing on top of your library. Christof -- okunah gmbh i.L. Software nach Maß Zugspitzstr. 211 www.okunah.de 86165 Augsburg cd@okunah.de Registergericht Augsburg Geschäftsführer Augsburg HRB 21896 Christof Donat UStID: DE 248 815 055
On 2013-11-12 07:13, Christof Donat wrote:
Hi,
I see use cases for printf and regex for instance, where the user provides a textual representation of something at compile time. In those cases, compile time validation of strings is a wonderful tool, and I have highest respect for it.
But in the context of sqlpp11 I don't see how or why I should use it? When you have SQL with its original syntax in your source code, you can copy/paste between your source code and your database tool. That way you can develop your queries iteratively. Compile time string parsing then adds compile time syntax checks, type safety and a intuitive interface to access
Am Montag, 11. November 2013, 23:16:06 schrieb Roland Bock: the columns in the result set by using your library.
The library is constructing the query string at runtime. There is no string to be validated at compile time. This is a major difference to most other C++ SQL libraries. The huge strength of it is to me, that it provides all that syntax and type checking and the intuitive access to the result sets. Its weakness is, that I have to translate my queries from SQL syntax to another similar, but not equal syntax when interactively working with my SQL tools. That could be resolved by adding compile time string parsing on top of your library.
Christof
As I wrote to Vicente, personally I wouldn't follow that path. I don't believe in ct SQL parsing for all but the simplest cases. But I might be totally wrong. If you have some spare time on your hands and consider such an approach useful, you should probably give it a try. Regards, Roland
On 11/12/2013 12:26 AM, Roland Bock wrote:
I don't believe in ct SQL parsing for all but the simplest cases. But I might be totally wrong.
FWIW, I agree with you. I like the looks of your EDSL. My suggestion: Look to the success of C#'s LINQ queries, and aim for that. If you can design your EDSL so it can be extended to bind to in-memory data structures, then it would be HUGE. Listen to your gut and don't be afraid to say no. -- Eric Niebler Boost.org http://www.boost.org
On 2013-11-12 19:03, Eric Niebler wrote:
On 11/12/2013 12:26 AM, Roland Bock wrote:
I don't believe in ct SQL parsing for all but the simplest cases. But I might be totally wrong. FWIW, I agree with you. I like the looks of your EDSL. :-) My suggestion: Look to the success of C#'s LINQ queries, and aim for that. If you can design your EDSL so it can be extended to bind to in-memory data structures, then it would be HUGE. Yeah, I am thinking about in-memory data structures and a few other suggestions.
Listen to your gut and don't be afraid to say no.
Will do so and won't be. Thanks again for your input! Best regards from Munich, Roland
On 2013-11-12 19:03, Eric Niebler wrote:
On 11/12/2013 12:26 AM, Roland Bock wrote:
[...] FWIW, I agree with you. I like the looks of your EDSL. My suggestion: Look to the success of C#'s LINQ queries, and aim for that. If you can design your EDSL so it can be extended to bind to in-memory data structures, then it would be HUGE.
FYI: By that definition, as of today, it is HUGE :-)
It is still a long way to go, of course, but I do have a working SQL
insert and select on std::vector
On 12/11/2013 19:13, Quoth Christof Donat:
When you have SQL with its original syntax in your source code, you can copy/paste between your source code and your database tool. That way you can develop your queries iteratively. Compile time string parsing then adds compile time syntax checks, type safety and a intuitive interface to access the columns in the result set by using your library.
As long as it's easy to translate from one to the other, it shouldn't be a big deal either way. The most important thing in this respect for sqlpp11 would be to have an easy way to take a chunk of code and extract the actual SQL query from it, perhaps via inserting an extra line in the development/production code to write it out to a log or to copy/paste it into some helper skeleton that does something similar. Converting back again should be fairly straightforward (albeit more manual) as long as you've only made minor tweaks to the query while iteratively testing it in the database tool, which is probably the common case. The comparative danger of using direct string parsing is that it's far too easy to insert something that the parser can't make sense of, and either way it's likely to increase compile times over an object-model-based approach.
On Mon, Nov 11, 2013 at 9:31 PM, Roland Bock
On 11/11/13 13:35, Abel Sinkovics wrote:
There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface.
Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples like the one above.
Do you mean that you "inline" the variable parameters values in the query text as opposed to using "placeholders" which you later "bind" with actual values? If that's the case, that's a big no-no on some RDBMS's like Oracle for performance reason (you need to reparse and replan, i.e, "recompile" the query and find again the optimal query execution plan), w/o even getting into SQL injections, etc... A SQL DB API w/o support for prepared queries which you can reuse with different bind values would be ineffective IMHO. Similarly, if you cannot bind array of values to limit round-trips to the DB (if supported; SQLite has no array interface for example), you can never achieve maximum possible performance. My $0.02 :). --DD
On 2013-11-12 09:39, Dominique Devienne wrote:
On Mon, Nov 11, 2013 at 9:31 PM, Roland Bock
wrote: There is a library (Metaparse) which supports creating a string-based interface for libraries which is processed at compile-time. It could be used to provide a string-based interface. Metaparse requires const char[N] arguments, right? That would be a rather atypical case for using a query interface, I'd say. Personally I have never used queries without variable parameters except in examples
On 11/11/13 13:35, Abel Sinkovics wrote: like the one above.
Do you mean that you "inline" the variable parameters values in the query text as opposed to using "placeholders" which you later "bind" with actual values? I meant that real life queries are not as simple as the case discussed above. If that's the case, that's a big no-no on some RDBMS's like Oracle for performance reason (you need to reparse and replan, i.e, "recompile" the query and find again the optimal query execution plan), w/o even getting into SQL injections, etc... A SQL DB API w/o support for prepared queries which you can reuse with different bind values would be ineffective IMHO. Similarly, if you cannot bind array of values to limit round-trips to the DB (if supported; SQLite has no array interface for example), you can never achieve maximum possible performance.
My $0.02 :). --DD
Thanks for the input. Prepared queries are in fact not supported. I'll read up on them and report back. Regards, Roland
On 13/11/2013 02:17, Quoth Roland Bock:
Thanks for the input. Prepared queries are in fact not supported. I'll read up on them and report back.
In SQL Server, queries are automatically "prepared" as long as you execute the same SQL statement as previously (so the only variations are in the parameter values). Provided that you do value insertion via actual parameters (either named or positional, depending on the target SQL dialect) and not via directly inserting constants into the SQL query itself, it should be fairly straightforward to extend to a dialect that requires explicit preparing; you'd just need to provide a syntax that allows you to cache a query object in a variable and then execute it multiple times with different actual values. (Using parameters is also preferred over direct insertion because it lessens the risk of script injection.)
On 2013-11-13 00:48, Gavin Lambert wrote:
On 13/11/2013 02:17, Quoth Roland Bock:
Thanks for the input. Prepared queries are in fact not supported. I'll read up on them and report back.
In SQL Server, queries are automatically "prepared" as long as you execute the same SQL statement as previously (so the only variations are in the parameter values). That would be perfect, of course.
Provided that you do value insertion via actual parameters (either named or positional, depending on the target SQL dialect) and not via directly inserting constants into the SQL query itself, it should be fairly straightforward to extend to a dialect that requires explicit preparing; you'd just need to provide a syntax that allows you to cache a query object in a variable and then execute it multiple times with different actual values. (Using parameters is also preferred over direct insertion because it lessens the risk of script injection.)
That should be relatively easy to do (and if you encapsulate the query generation in a parametrized function it is already there). Thanks and regards, Roland
On 13/11/2013 18:24, Quoth Roland Bock:
On 2013-11-13 00:48, Gavin Lambert wrote:
Provided that you do value insertion via actual parameters (either named or positional, depending on the target SQL dialect) and not via directly inserting constants into the SQL query itself, it should be fairly straightforward to extend to a dialect that requires explicit preparing; you'd just need to provide a syntax that allows you to cache a query object in a variable and then execute it multiple times with different actual values. (Using parameters is also preferred over direct insertion because it lessens the risk of script injection.)
That should be relatively easy to do (and if you encapsulate the query generation in a parametrized function it is already there).
I'm not sure I'm reading that last part right, but what I am talking about is very different from having a C++ function that accepts parameters and internally generates and runs the query. I haven't looked at your code enough to formulate a sensible example using its syntax, but if I can offer an analogy using Boost.Format's syntax (don't take it too literally): boost::format GenerateQuery() { return boost::format("select * from T where a=%1% and b=%2%"); // not intended to be fully safe SQL, it's just for show // also I'm not implying you should use strings to construct } std::string IndirectCall(const boost::format& query, int a, const std::string& b) { return (query % a % b).str(); // imagine this actually runs SQL } std::string DirectCall(int a, const std::string& b) { IndirectCall(GenerateQuery(), a, b); } The GenerateQuery method returns a "query object" that defines the action to be done but not the specific parameter values on which it acts. It is expensive to create (because this is where all the parsing happens) but is (fairly) cheap to copy, and reusable without copying. This is analogous to preparing an SQL query. Outside code could run GenerateQuery once (perhaps at program startup), cache the result somewhere, and then use it repeatedly via IndirectCall, thereby only paying the parsing cost once. (Boost.Format allows the same object to be reused once str() is invoked on it, or if explicitly cleared; or you can copy it prior to adding actual values to the copy.) This is analogous to running a prepared query with different actual parameters. (The db connector backend usually has specific separation between the SQL statement and the parameters -- you wouldn't actually insert them into the string the way that this example implies. This also typically means you don't have to escape values to prevent injection, as they're already known to be data.) The DirectCall method is the least performant case; it explicitly regenerates the query each time. This is analogous to running an unprepared query *or* actually inserting the real values directly into the SQL instead of using the db-connector's parameter mechanism. (But this is the most common way that SQL gets used, because it's "easier". I haven't looked too closely but I get the impression that this is the only way that sqlpp11 works at present too.) The key point being that for prepared statements to work (whether explicit as in Oracle or automatic as in SQL Server), you need to be able to define the SQL statement itself using only constants and placeholders where variables go, but not provide the actual values of the variables. Then when you actually go to execute the query you pass the constant SQL statement along with the collection of actual values to fill in the placeholders for this particular invocation. Different database backends support different syntaxes for placeholders; most support positional placeholders and a few offer named placeholders. (In your case I would recommend using only named placeholders on the user side, because it will be very unobvious to the user what the "real" positional order in the underlying SQL query will be. When interfacing to a connector for a DB that only supports positional placeholders you would have to convert them appropriately.)
On Wed, Nov 13, 2013 at 7:28 AM, Gavin Lambert
The GenerateQuery method returns a "query object" that defines the action to be done but not the specific parameter values on which it acts. It is expensive to create (because this is where all the parsing happens) but is (fairly) cheap to copy, and reusable without copying. This is analogous to preparing an SQL query.
I agree with Gavin's points. Support for prepared queries returning some kind of functor / lambda taking as many typed variables as the query has placeholders is essential IMHO, because unless you have true binding of in-memory data, it's a "toy" to run literal SQL only. (whether it's the SQL string literal parsed at compile time, or a compile time DSL matters less to me). But I'd like to flesh out my point that array-binding is essential to performance. Most commenters focus on queries, i.e. selects, where you send little data (just a few bind variables) and receive lots of data. That can be made efficient in Oracle just by turning on prefetching on the statement handle, w/o any other change to the client code. It's the OCI client library which does the work for you transparently. But when you insert or update, you have to send a lot more data than you receive, and if you do a round-trip to the server on every row, you get orders of magnitude performance differences sometimes. For example, inserting 10,000 rows in a two-column table (an int ranging from 1 to 10,000, and a fixed "payload" string for all rows that read "azertyuiop"), doing it the "normal" way takes 0.578 sec, while sending the 10,000 rows at once take 0.007 sec. This timing is with the Oracle server and the client on the same Windows laptop. (the same test running on Linux, talking to a Linux server in the same room over the LAN gives 0.310 sec for scalar inserts, < 0.001 sec for array insert). Increase the latency by using a server on the WAN, and the difference can be even worse. But unlike the select case, there's no prefetching equivalent, you must use separate APIs to bind, and it can be done differently too (one array per col, or one array of struct with a member for each col, or using dynamic binding via callbacks that OCI calls to fill its "buffers" before sending the whole lot server-side). An API like SQLite doesn't need array-binding, because it's not client-server, but server round-trips is what makes or breaks an application's performance when dealing with databases, at least for those which deal with high volume of data, which is often one of the primary reason they are coded in C++ in the first place. An API like sqlpp11 cannot be everything to everyone of course. You're very much entitled to decide for yourself what is in-bounds and out-of-bounds. But I thought I'd provide more info on the above so that you at least are more aware of the problem space as I see it myself. Cheers, --DD
On 2013-11-13 12:09, Dominique Devienne wrote:
On Wed, Nov 13, 2013 at 7:28 AM, Gavin Lambert
wrote: The GenerateQuery method returns a "query object" that defines the action to be done but not the specific parameter values on which it acts. It is expensive to create (because this is where all the parsing happens) but is (fairly) cheap to copy, and reusable without copying. This is analogous to preparing an SQL query.
I agree with Gavin's points. Support for prepared queries returning some kind of functor / lambda taking as many typed variables as the query has placeholders is essential IMHO, because unless you have true binding of in-memory data, it's a "toy" to run literal SQL only. (whether it's the SQL string literal parsed at compile time, or a compile time DSL matters less to me).
But I'd like to flesh out my point that array-binding is essential to performance. Most commenters focus on queries, i.e. selects, where you send little data (just a few bind variables) and receive lots of data. That can be made efficient in Oracle just by turning on prefetching on the statement handle, w/o any other change to the client code. It's the OCI client library which does the work for you transparently.
But when you insert or update, you have to send a lot more data than you receive, and if you do a round-trip to the server on every row, you get orders of magnitude performance differences sometimes. For example, inserting 10,000 rows in a two-column table (an int ranging from 1 to 10,000, and a fixed "payload" string for all rows that read "azertyuiop"), doing it the "normal" way takes 0.578 sec, while sending the 10,000 rows at once take 0.007 sec. This timing is with the Oracle server and the client on the same Windows laptop. (the same test running on Linux, talking to a Linux server in the same room over the LAN gives 0.310 sec for scalar inserts, < 0.001 sec for array insert). Increase the latency by using a server on the WAN, and the difference can be even worse. But unlike the select case, there's no prefetching equivalent, you must use separate APIs to bind, and it can be done differently too (one array per col, or one array of struct with a member for each col, or using dynamic binding via callbacks that OCI calls to fill its "buffers" before sending the whole lot server-side).
An API like SQLite doesn't need array-binding, because it's not client-server, but server round-trips is what makes or breaks an application's performance when dealing with databases, at least for those which deal with high volume of data, which is often one of the primary reason they are coded in C++ in the first place.
An API like sqlpp11 cannot be everything to everyone of course. You're very much entitled to decide for yourself what is in-bounds and out-of-bounds. But I thought I'd provide more info on the above so that you at least are more aware of the problem space as I see it myself.
Cheers, --DD
Gavin, Dominique, Thanks to both of you for clarification and explanations! In fact, I misread Gavins mail and as I answered to Dominique a few days ago, I need to read up on prepared statements. I'll probably come back to you about that. Best regards, Roland
Roland Bock 2013-11-10 02:03:
Hi,
Hi, Code depends on boost? -- Regards, niXman ___________________________________________________ Dual-target(32 & 64-bit) MinGW-W64 compilers for 32 and 64-bit Windows: http://sourceforge.net/projects/mingw-w64/ ___________________________________________________ Another online IDE: http://liveworkspace.org/
On 2013-11-10 02:38, niXman wrote:
Roland Bock 2013-11-10 02:03:
Hi,
Hi,
Code depends on boost?
No, sqlpp11 itself only depends on a decent C++11 compiler and matching STL. I test with clang-3.2 and gcc-4.8 on Ubuntu as of now. We are using a version slightly older than the current release with clang-3.1 on FreeBSD. Connectors could have other dependencies of course. The current connectors for MySQL/MariaDb and Sqlite3 depend on the respective C-client libraries. The MySQL connector currently requires the thread_local feature, so clang3.2 won't work, but I think I'll replace that with std::call_once. Regards, Roland
On 11/09/13 16:03, Roland Bock wrote:
Hi,
over the last four or five years I developed several SQL libraries for C++. With C++11 I was finally able to create an SQL library that meets my own expectations and requirements. It is being used in production, I recently put it on github, and I would really like to hear from you guys whether this is something that could be interesting for you personally or for boost?
https://github.com/rbock/sqlpp11 https://github.com/rbock/sqlpp11/wiki
[snip]
The code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/table_base.h
contains:
template
On 11/10/13 18:02, Larry Evans wrote:
On 11/09/13 16:03, Roland Bock wrote:
Hi,
over the last four or five years I developed several SQL libraries for C++. With C++11 I was finally able to create an SQL library that meets my own expectations and requirements. It is being used in production, I recently put it on github, and I would really like to hear from you guys whether this is something that could be interesting for you personally or for boost?
https://github.com/rbock/sqlpp11 https://github.com/rbock/sqlpp11/wiki
[snip] The code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/table_base.h
contains:
template
struct table_base_t : public ColumnSpec::_name_t::template _member_t >... {...}; which looks like it declares a table row, where the columns come from ColumnSpec...
What's confusing to me is the code here:
https://github.com/rbock/sqlpp11/wiki/Tables
contains, what I assume are the ColumnSpecs; however, there's no column_t, there's only column_type. Does table_base.h have a typo?
OOPS. I see it now: https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/column.h
On 2013-11-11 01:47, Larry Evans wrote:
On 11/10/13 18:02, Larry Evans wrote:
On 11/09/13 16:03, Roland Bock wrote:
Hi,
over the last four or five years I developed several SQL libraries for C++. With C++11 I was finally able to create an SQL library that meets my own expectations and requirements. It is being used in production, I recently put it on github, and I would really like to hear from you guys whether this is something that could be interesting for you personally or for boost?
https://github.com/rbock/sqlpp11 https://github.com/rbock/sqlpp11/wiki
[snip] The code here:
https://github.com/rbock/sqlpp11/blob/master/include/sqlpp11/table_base.h
contains:
template
struct table_base_t : public ColumnSpec::_name_t::template _member_t >... {...}; which looks like it declares a table row, where the columns come from ColumnSpec...
In understanding how the library works, this is probably one of the crucial parts. As you guessed correctly, it declares the innards of a table class. The somewhat convoluted looking inheritance adds columns as members to the class. The column_t