diff --git a/.gitignore b/.gitignore index e3fa2bcd..53d8c516 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ config.log config.status Makefile +sqlite_modern_cpp.pc tests/* !tests/*.cc diff --git a/README.md b/README.md index 6abc41cd..4f2dd075 100644 --- a/README.md +++ b/README.md @@ -13,141 +13,158 @@ using namespace std; int main() { - try { - // creates a database file 'dbfile.db' if it does not exists. - database db("dbfile.db"); - - // executes the query and creates a 'user' table - db << - "create table if not exists user (" - " _id integer primary key autoincrement not null," - " age int," - " name text," - " weight real" - ");"; - - // inserts a new user record. - // binds the fields to '?' . - // note that only types allowed for bindings are : - // int ,long, long long, float, double - // string , u16string - // sqlite3 only supports utf8 and utf16 strings, you should use std::string for utf8 and std::u16string for utf16. - // note that u"my text" is a utf16 string literal of type char16_t * . - db << "insert into user (age,name,weight) values (?,?,?);" - << 20 - << u"bob" - << 83.25; - - int age = 21; - float weight = 68.5; - string name = "jack"; - db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string - << age - << name - << weight; - - cout << "The new record got assigned id " << db.last_insert_rowid() << endl; - - // slects from user table on a condition ( age > 18 ) and executes - // the lambda for each row returned . - db << "select age,name,weight from user where age > ? ;" - << 18 - >> [&](int age, string name, double weight) { - cout << age << ' ' << name << ' ' << weight << endl; - }; - - // selects the count(*) from user table - // note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string - int count = 0; - db << "select count(*) from user" >> count; - cout << "cout : " << count << endl; - - // you can also extract multiple column rows - db << "select age, name from user where _id=1;" >> tie(age, name); - cout << "Age = " << age << ", name = " << name << endl; - - // this also works and the returned value will be automatically converted to string - string str_count; - db << "select count(*) from user" >> str_count; - cout << "scount : " << str_count << endl; - } - catch (exception& e) { - cout << e.what() << endl; - } + try { + // creates a database file 'dbfile.db' if it does not exists. + database db("dbfile.db"); + + // executes the query and creates a 'user' table + db << + "create table if not exists user (" + " _id integer primary key autoincrement not null," + " age int," + " name text," + " weight real" + ");"; + + // inserts a new user record. + // binds the fields to '?' . + // note that only types allowed for bindings are : + // int ,long, long long, float, double + // string , u16string + // sqlite3 only supports utf8 and utf16 strings, you should use std::string for utf8 and std::u16string for utf16. + // note that u"my text" is a utf16 string literal of type char16_t * . + db << "insert into user (age,name,weight) values (?,?,?);" + << 20 + << u"bob" + << 83.25; + + int age = 21; + float weight = 68.5; + string name = "jack"; + db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string + << age + << name + << weight; + + cout << "The new record got assigned id " << db.last_insert_rowid() << endl; + + // slects from user table on a condition ( age > 18 ) and executes + // the lambda for each row returned . + db << "select age,name,weight from user where age > ? ;" + << 18 + >> [&](int age, string name, double weight) { + cout << age << ' ' << name << ' ' << weight << endl; + }; + + // selects the count(*) from user table + // note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string + int count = 0; + db << "select count(*) from user" >> count; + cout << "cout : " << count << endl; + + // you can also extract multiple column rows + db << "select age, name from user where _id=1;" >> tie(age, name); + cout << "Age = " << age << ", name = " << name << endl; + + // this also works and the returned value will be automatically converted to string + string str_count; + db << "select count(*) from user" >> str_count; + cout << "scount : " << str_count << endl; + } + catch (exception& e) { + cout << e.what() << endl; + } } ``` -Prepared Statements +You can not execute multiple statements separated by semicolons in one go. + +Additional flags ---- -It is possible to retain and reuse statments this will keep the query plan and in case of an complex query or many uses might increase the performance significantly. +You can pass additional open flags to SQLite by using a config object: ```c++ - database db(":memory:"); - - // if you use << on a sqlite::database you get a prepared statment back - // this will not be executed till it gets destroyed or you execute it explicitly - auto ps = db << "select a,b from table where something = ? and anotherthing = ?"; // get a prepared parsed and ready statment - - // first if needed bind values to it - ps << 5; - int tmp = 8; - ps << tmp; - - // now you can execute it with `operator>>` or `execute()`. - // If the statement was executed once it will not be executed again when it goes out of scope. - // But beware that it will execute on destruction if it wasn't executed! - ps >> [&](int a,int b){ ... }; - - // after a successfull execution the statment needs to be reset to be execute again. This will reset the bound values too! - ps.reset(); - - // If you dont need the returned values you can execute it like this - ps.execute(); // the statment will not be reset! - - // there is a convinience operator to execute and reset in one go - ps++; - - // To disable the execution of a statment when it goes out of scope and wasn't used - ps.used(true); // or false if you want it to execute even if it was used +sqlite_config config; +config.flags = OpenFlags::READONLY +database db("some_db", config); +int a; +// Now you can only read from db +auto ps = db << "select a from table where something = ? and anotherthing = ?" >> a; +config.flags = OpenFlags::READWRITE | OpenFlags::CREATE; // This is the default +config.encoding = Encoding::UTF16; // The encoding is respected only if you create a new database +database db2("some_db2", config); +// If some_db2 didn't exists before, it will be created with UTF-16 encoding. +``` - // Usage Example: +Prepared Statements +---- +It is possible to retain and reuse statments this will keep the query plan and in case of an complex query or many uses might increase the performance significantly. - auto ps = db << "insert into complex_table_with_lots_of_indices values (?,?,?)"; - int i = 0; - while( i < 100000 ){ - ps << long_list[i++] << long_list[i++] << long_list[i++]; - ps++; - } +```c++ +database db(":memory:"); + +// if you use << on a sqlite::database you get a prepared statment back +// this will not be executed till it gets destroyed or you execute it explicitly +auto ps = db << "select a,b from table where something = ? and anotherthing = ?"; // get a prepared parsed and ready statment + +// first if needed bind values to it +ps << 5; +int tmp = 8; +ps << tmp; + +// now you can execute it with `operator>>` or `execute()`. +// If the statement was executed once it will not be executed again when it goes out of scope. +// But beware that it will execute on destruction if it wasn't executed! +ps >> [&](int a,int b){ ... }; + +// after a successfull execution the statment can be executed again, but the bound values are resetted. +// If you dont need the returned values you can execute it like this +ps++; // Does reset the bound values +// or like this +ps.execute(); // Does NOT reset the bound values, but we can reset them manually: +ps.reset(); + +// To disable the execution of a statment when it goes out of scope and wasn't used +ps.used(true); // or false if you want it to execute even if it was used + +// Usage Example: + +auto ps = db << "insert into complex_table_with_lots_of_indices values (?,?,?)"; +int i = 0; +while( i < 100000 ){ + ps << long_list[i++] << long_list[i++] << long_list[i++]; + ps++; // Equal to: ps.execute(); ps.reset(); +} ``` Shared Connections ---- If you need the handle to the database connection to execute sqlite3 commands directly you can get a managed shared_ptr to it, so it will not close as long as you have a referenc to it. -Take this example on how to deal with a database backup using SQLITEs own functions in a save and modern way. +Take this example on how to deal with a database backup using SQLITEs own functions in a safe and modern way. ```c++ - try { - database backup("backup"); //Open the database file we want to backup to - - auto con = db.connection(); // get a handle to the DB we want to backup in our scope - // this way we are sure the DB is open and ok while we backup - - // Init Backup and make sure its freed on exit or exceptions! - auto state = - std::unique_ptr( - sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"), - sqlite3_backup_finish - ); - - if(state) { - int rc; - // Each iteration of this loop copies 500 database pages from database db to the backup database. - do { - rc = sqlite3_backup_step(state.get(), 500); - std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n"; - } while(rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); - } - } // Release allocated resources. +try { + database backup("backup"); //Open the database file we want to backup to + + auto con = db.connection(); // get a handle to the DB we want to backup in our scope + // this way we are sure the DB is open and ok while we backup + + // Init Backup and make sure its freed on exit or exceptions! + auto state = + std::unique_ptr( + sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"), + sqlite3_backup_finish + ); + + if(state) { + int rc; + // Each iteration of this loop copies 500 database pages from database db to the backup database. + do { + rc = sqlite3_backup_step(state.get(), 500); + std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n"; + } while(rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); + } +} // Release allocated resources. ``` Transactions @@ -155,23 +172,23 @@ Transactions You can use transactions with `begin;`, `commit;` and `rollback;` commands. ```c++ - db << "begin;"; // begin a transaction ... - db << "insert into user (age,name,weight) values (?,?,?);" - << 20 - << u"bob" - << 83.25f; - db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string - << 21 - << u"jack" - << 68.5; - db << "commit;"; // commit all the changes. - - db << "begin;"; // begin another transaction .... - db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string - << 19 - << u"chirs" - << 82.7; - db << "rollback;"; // cancel this transaction ... +db << "begin;"; // begin a transaction ... +db << "insert into user (age,name,weight) values (?,?,?);" + << 20 + << u"bob" + << 83.25f; +db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string + << 21 + << u"jack" + << 68.5; +db << "commit;"; // commit all the changes. + +db << "begin;"; // begin another transaction .... +db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string + << 19 + << u"chirs" + << 82.7; +db << "rollback;"; // cancel this transaction ... ``` @@ -181,16 +198,16 @@ Use `std::vector` to store and retrieve blob data. `T` could be `char,short,int,long,long long, float or double`. ```c++ - db << "CREATE TABLE person (name TEXT, numbers BLOB);"; - db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; - db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; +db << "CREATE TABLE person (name TEXT, numbers BLOB);"; +db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; +db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; - vector numbers_bob; - db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; +vector numbers_bob; +db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; - db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector numbers_sara){ - for(auto e : numbers_sara) cout << e << ' '; cout << endl; - }; +db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector numbers_sara){ + for(auto e : numbers_sara) cout << e << ' '; cout << endl; +}; ``` NULL values @@ -224,86 +241,179 @@ db << "select age,name,img from tbl where id = 2" }; ``` -NULL values (DEPRICATED) +SQLCipher +---- + +We have native support for [SQLCipher](https://www.zetetic.net/sqlcipher/). +If you want to use encrypted databases, include the `sqlite_moder_cpp/sqlcipher.h` header. +Then create a `sqlcipher_database` instead. + +```c++ +#include +#include +using namespace sqlite; +using namespace std; + +int main() { + try { + // creates a database file 'dbfile.db' if it does not exists with password 'secret' + sqlcipher_config config; + config.key = secret; + sqlcipher_database db("dbfile.db", config); + + // executes the query and creates a 'user' table + db << + "create table if not exists user (" + " _id integer primary key autoincrement not null," + " age int," + " name text," + " weight real" + ");"; + + // More queries ... + db.rekey("new_secret"); // Change the password of the already encrypted database. + + // Even more queries .. + } + catch (exception& e) { cout << e.what() << endl; } +} +``` + +NULL values (C++17) +---- +You can use `std::optional` as an alternative for `std::unique_ptr` to work with NULL values. + +```c++ +#include + +struct User { + long long _id; + std::optional age; + std::optional name; + std::optional weight; +}; + +int main() { + User user; + user.name = "bob"; + + // Same database as above + database db("dbfile.db"); + + // Here, age and weight will be inserted as NULL in the database. + db << "insert into user (age,name,weight) values (?,?,?);" + << user.age + << user.name + << user.weight; + user._id = db.last_insert_rowid(); + + // Here, the User instance will retain the NULL value(s) from the database. + db << "select _id,age,name,weight from user where age > ? ;" + << 18 + >> [&](long long id, + std::optional age, + std::optional name + std::optional weight) { + + cout << "id=" << _id + << " age = " << (age ? to_string(*age) ? string("NULL")) + << " name = " << (name ? *name : string("NULL")) + << " weight = " << (weight ? to_string(*weight) : string(NULL)) + << endl; + }; +} +``` + +If you do not have C++17 support, you can use boost optional instead by defining `_MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT` before importing the `sqlite_modern_cpp` header. + +If the optional library is not available, the experimental/optional one will be used instead. + +**Note: boost support is deprecated and will be removed in future versions.** + +Variant type support (C++17) ---- -**Note: this option is deprecated and will be removed in future versions.** -You can enable boost support by defining _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT before importing sqlite_modern_cpp header. +If your columns may have flexible types, you can use C++17's `std::variant` to extract the value. ```c++ - #define _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT - #include - - struct User { - long long _id; - boost::optional age; - boost::optional name; - boost::optional weight; - }; - - { - User user; - user.name = "bob"; - - // Same database as above - database db("dbfile.db"); - - // Here, age and weight will be inserted as NULL in the database. - db << "insert into user (age,name,weight) values (?,?,?);" - << user.age - << user.name - << user.weight; - - user._id = db.last_insert_rowid(); - } - - { - // Here, the User instance will retain the NULL value(s) from the database. - db << "select _id,age,name,weight from user where age > ? ;" - << 18 - >> [&](long long id, - boost::optional age, - boost::optional name - boost::optional weight) { - - User user; - user._id = id; - user.age = age; - user.name = move(name); - user.weight = weight; - - cout << "id=" << user._id - << " age = " << (user.age ? to_string(*user.age) ? string("NULL")) - << " name = " << (user.name ? *user.name : string("NULL")) - << " weight = " << (user.weight ? to_string(*user.weight) : string(NULL)) - << endl; +db << "CREATE TABLE tbl (id integer, data);"; +db << "INSERT INTO tbl VALUES (?, ?);" << 1 << vector { 1, 2, 3}; +db << "INSERT INTO tbl VALUES (?, ?);" << 2 << 2.5; + +db << "select data from tbl where id = 1" + >> [](std::variant, double> data) { + if(data.index() != 1) { + cerr << "ERROR: we expected a blob" << std::endl; + } + + for(auto i : get>(data)) cout << i << ","; cout << endl; + }; + +db << "select data from tbl where id = 2" + >> [](std::variant, double> data) { + if(data.index() != 2) { + cerr << "ERROR: we expected a real number" << std::endl; + } + + cout << get(data) << endl; }; - } ``` +If you read a specific type and this type does not match the actual type in the SQlite database, yor data will be converted. +This does not happen if you use a `variant`. +If the `variant` does an alternative of the same value type, an `mismatch` exception will be thrown. +The value types are NULL, integer, real number, text and BLOB. +To support all possible values, you can use `variant`. + Errors ---- -On error, the library throws an error class indicating the type of error. The error classes are derived from the SQLITE3 error names, so if the error code is SQLITE_CONSTRAINT, the error class thrown is sqlite::exceptions::constraint. Note that all errors are derived from sqlite::sqlite_exception and that itself is derived from std::runtime_exception. -sqlite::sqlite_exception has a `get_code()` member function to get the SQLITE3 error code. +On error, the library throws an error class indicating the type of error. The error classes are derived from the SQLITE3 error names, so if the error code is SQLITE_CONSTRAINT, the error class thrown is sqlite::errors::constraint. SQLite3 extended error names are supported too. So there is e.g. a class sqlite::errors::constraint_primarykey derived from sqlite::errors::constraint. Note that all errors are derived from sqlite::sqlite_exception and that itself is derived from std::runtime_exception. +sqlite::sqlite_exception has a `get_code()` member function to get the SQLITE3 error code or `get_extended_code()` to get the extended error code. Additionally you can use `get_sql()` to see the SQL statement leading to the error. ```c++ - database db(":memory:"); - db << "create table person (id integer primary key not null, name text);"; - - try { - db << "insert into person (id, name) values (?,?)" << 1 << "jack"; - // inserting again to produce error - db << "insert into person (id, name) values (?,?)" << 1 << "jack"; - } - /* if you are trying to catch all sqlite related exceptions - * make sure to catch them by reference */ - catch (sqlite_exception& e) { - cerr << e.get_code() << ": " << e.what() << " during " - << e.get_sql() << endl; - } - /* you can catch specific exceptions as well, - catch(sqlite::exceptions::constraint e) { } */ +database db(":memory:"); +db << "create table person (id integer primary key not null, name text);"; + +try { + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; +} +/* if you are trying to catch all sqlite related exceptions + * make sure to catch them by reference */ +catch (sqlite_exception& e) { + cerr << e.get_code() << ": " << e.what() << " during " + << e.get_sql() << endl; +} +/* you can catch specific exceptions as well, + catch(sqlite::errors::constraint e) { } */ +/* and even more specific exceptions + catch(sqlite::errors::constraint_primarykey e) { } */ +``` + +You can also register a error logging function with `sqlite::error_log`. +The `` header has to be included to make this function available. +The call to `sqlite::error_log` has to be the first call to any `sqlite_modern_cpp` function by your program. + +```c++ +error_log( + [&](sqlite_exception& e) { + cerr << e.get_code() << ": " << e.what() << endl; + }, + [&](errors::misuse& e) { + /* You can behave differently to specific errors */ + } +); +database db(":memory:"); +db << "create table person (id integer primary key not null, name text);"; + +try { + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; +} +catch (sqlite_exception& e) {} ``` Custom SQL functions @@ -312,65 +422,23 @@ Custom SQL functions To extend SQLite with custom functions, you just implement them in C++: ```c++ - database db(":memory:"); - db.define("tgamma", [](double i) {return std::tgamma(i);}); - db << "CREATE TABLE numbers (number INTEGER);"; +database db(":memory:"); +db.define("tgamma", [](double i) {return std::tgamma(i);}); +db << "CREATE TABLE numbers (number INTEGER);"; - for(auto i=0; i!=10; ++i) - db << "INSERT INTO numbers VALUES (?);" << i; +for(auto i=0; i!=10; ++i) + db << "INSERT INTO numbers VALUES (?);" << i; - db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) { - cout << number << "! = " << factorial << '\n'; - }; +db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) { + cout << number << "! = " << factorial << '\n'; +}; ``` - NDK support ---- Just Make sure you are using the full path of your database file : `sqlite::database db("/data/data/com.your.package/dbfile.db")`. -SQLCipher ----- - -The library has native support for [SQLCipher](https://www.zetetic.net/sqlcipher/). If you want to use encrypted databases, you have to include the `sqlite_moder_cpp/sqlcipher.h` header. -Then you can create a `sqlcipher_database`. - -```c++ -#include -#include -using namespace sqlite; -using namespace std; - -int main() { - - try { - // creates a database file 'dbfile.db' if it does not exists with password 'secret' - sqlcipher_config config; - config.key = secret; - sqlcipher_database db("dbfile.db", config); - - // executes the query and creates a 'user' table - db << - "create table if not exists user (" - " _id integer primary key autoincrement not null," - " age int," - " name text," - " weight real" - ");"; - - // More queries - - db.rekey("new_secret"); // Change the password of the already encrypted database. - - // Even more queries - } - catch (exception& e) { - cout << e.what() << endl; - } -} -``` - Building and Installing ---- @@ -385,18 +453,17 @@ Note, there's nothing to make, so you there's no need to run configure and you c Breaking Changes ---- - -- Databases with non-ASCII characters in their names created with versions up to 2.4 are not found by the current master. -You have to manually rename them to their actual (UTF-8 encoded) name. +See breaking changes documented in each [Release](https://github.com/aminroosta/sqlite_modern_cpp/releases). Package managers ---- Pull requests are welcome :wink: - [AUR](https://aur.archlinux.org/packages/sqlite_modern_cpp/) Arch Linux - - maintainer [Nissar Chababy](https://github.com/funilrys) + - maintainer [Nissar Chababy](https://github.com/funilrys) - Nuget (TODO [nuget.org](https://www.nuget.org/)) - Conan (TODO [conan.io](https://conan.io/)) +- [vcpkg](https://github.com/Microsoft/vcpkg) -##License +## License MIT license - [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) diff --git a/configure b/configure index 72b65037..da47d6d3 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for sqlite_modern_cpp version-0.7. +# Generated by GNU Autoconf 2.69 for sqlite_modern_cpp 3.2. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -577,8 +577,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='sqlite_modern_cpp' PACKAGE_TARNAME='sqlite_modern_cpp' -PACKAGE_VERSION='version-0.7' -PACKAGE_STRING='sqlite_modern_cpp version-0.7' +PACKAGE_VERSION='3.2' +PACKAGE_STRING='sqlite_modern_cpp 3.2' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1226,7 +1226,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sqlite_modern_cpp version-0.7 to adapt to many kinds of systems. +\`configure' configures sqlite_modern_cpp 3.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1288,7 +1288,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite_modern_cpp version-0.7:";; + short | recursive ) echo "Configuration of sqlite_modern_cpp 3.2:";; esac cat <<\_ACEOF @@ -1368,7 +1368,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite_modern_cpp configure version-0.7 +sqlite_modern_cpp configure 3.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1666,7 +1666,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sqlite_modern_cpp $as_me version-0.7, which was +It was created by sqlite_modern_cpp $as_me 3.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2614,6 +2614,47 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + if test $cvd_conf_test = 1 + then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ts_success=yes + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + + if test "" == "" + then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -pedantic works" >&5 +$as_echo_n "checking if compiler flag -pedantic works... " >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 +$as_echo_n "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -pedantic" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + cvd_conf_test=1 +else + cvd_conf_test=0 +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + + + + if test $cvd_conf_test = 1 then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 @@ -2889,7 +2930,7 @@ $as_echo "$PKGCONFIG_LIBDIR" >&6; } # This will be put into the pc file - VERSION=version-0.7 + VERSION=3.2 fi @@ -3899,7 +3940,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sqlite_modern_cpp $as_me version-0.7, which was +This file was extended by sqlite_modern_cpp $as_me 3.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -3952,7 +3993,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -sqlite_modern_cpp config.status version-0.7 +sqlite_modern_cpp config.status 3.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 8f32a938..87b766b9 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(sqlite_modern_cpp, version-0.7) +AC_INIT(sqlite_modern_cpp, 3.2) AC_LANG(C++) AC_PROG_CXX @@ -56,10 +56,11 @@ define([TEST_AND_SET_CXXFLAG],[ TEST_AND_SET_CXXFLAG(-Wall) TEST_AND_SET_CXXFLAG(-Wextra) +TEST_AND_SET_CXXFLAG(-pedantic) TEST_AND_SET_CXXFLAG(-W) TEST_AND_SET_CXXFLAG(-O3) TEST_AND_SET_CXXFLAG(-Werror) -TEST_AND_SET_CXXFLAG(-std=c++11) +TEST_AND_SET_CXXFLAG(-std=c++14) # diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 9f635a12..344dedcb 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -1,112 +1,72 @@ #pragma once +#include +#include #include #include -#include #include #include #include #include +#define MODERN_SQLITE_VERSION 3002008 + +#ifdef __has_include +#if __cplusplus > 201402 && __has_include() +#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT +#elif __has_include() +#define MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT +#endif +#endif + #ifdef __has_include -#if __has_include() -#define _MODERN_SQLITE_STD_OPTIONAL_SUPPORT +#if __cplusplus > 201402 && __has_include() +#define MODERN_SQLITE_STD_VARIANT_SUPPORT #endif #endif -#ifdef _MODERN_SQLITE_STD_OPTIONAL_SUPPORT +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT #include #endif +#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT +#include +#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT +#endif + #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT #include #endif +#ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ +#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 100000 +#undef __cpp_lib_uncaught_exceptions +#endif +#endif + #include +#include "sqlite_modern_cpp/errors.h" #include "sqlite_modern_cpp/utility/function_traits.h" #include "sqlite_modern_cpp/utility/uncaught_exceptions.h" +#include "sqlite_modern_cpp/utility/utf16_utf8.h" -namespace sqlite { +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT +#include "sqlite_modern_cpp/utility/variant.h" +#endif - class sqlite_exception: public std::runtime_error { - public: - sqlite_exception(const char* msg, std::string sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} - sqlite_exception(int code, std::string sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {} - int get_code() {return code;} - std::string get_sql() {return sql;} - private: - int code; - std::string sql; - }; +namespace sqlite { - namespace exceptions { - //One more or less trivial derived error class for each SQLITE error. - //Note the following are not errors so have no classes: - //SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE - // - //Note these names are exact matches to the names of the SQLITE error codes. - class error: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class internal: public sqlite_exception{ using sqlite_exception::sqlite_exception; }; - class perm: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class abort: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class busy: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class locked: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class nomem: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class readonly: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class interrupt: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class ioerr: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class corrupt: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class notfound: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class full: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class cantopen: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class protocol: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class empty: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class schema: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class toobig: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class constraint: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class mismatch: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class misuse: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class nolfs: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class auth: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class format: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class range: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class notadb: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - - //Some additional errors are here for the C++ interface - class more_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - class no_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - - static void throw_sqlite_error(const int& error_code, const std::string &sql = "") { - if(error_code == SQLITE_ERROR) throw exceptions::error(error_code, sql); - else if(error_code == SQLITE_INTERNAL) throw exceptions::internal (error_code, sql); - else if(error_code == SQLITE_PERM) throw exceptions::perm(error_code, sql); - else if(error_code == SQLITE_ABORT) throw exceptions::abort(error_code, sql); - else if(error_code == SQLITE_BUSY) throw exceptions::busy(error_code, sql); - else if(error_code == SQLITE_LOCKED) throw exceptions::locked(error_code, sql); - else if(error_code == SQLITE_NOMEM) throw exceptions::nomem(error_code, sql); - else if(error_code == SQLITE_READONLY) throw exceptions::readonly(error_code, sql); - else if(error_code == SQLITE_INTERRUPT) throw exceptions::interrupt(error_code, sql); - else if(error_code == SQLITE_IOERR) throw exceptions::ioerr(error_code, sql); - else if(error_code == SQLITE_CORRUPT) throw exceptions::corrupt(error_code, sql); - else if(error_code == SQLITE_NOTFOUND) throw exceptions::notfound(error_code, sql); - else if(error_code == SQLITE_FULL) throw exceptions::full(error_code, sql); - else if(error_code == SQLITE_CANTOPEN) throw exceptions::cantopen(error_code, sql); - else if(error_code == SQLITE_PROTOCOL) throw exceptions::protocol(error_code, sql); - else if(error_code == SQLITE_EMPTY) throw exceptions::empty(error_code, sql); - else if(error_code == SQLITE_SCHEMA) throw exceptions::schema(error_code, sql); - else if(error_code == SQLITE_TOOBIG) throw exceptions::toobig(error_code, sql); - else if(error_code == SQLITE_CONSTRAINT) throw exceptions::constraint(error_code, sql); - else if(error_code == SQLITE_MISMATCH) throw exceptions::mismatch(error_code, sql); - else if(error_code == SQLITE_MISUSE) throw exceptions::misuse(error_code, sql); - else if(error_code == SQLITE_NOLFS) throw exceptions::nolfs(error_code, sql); - else if(error_code == SQLITE_AUTH) throw exceptions::auth(error_code, sql); - else if(error_code == SQLITE_FORMAT) throw exceptions::format(error_code, sql); - else if(error_code == SQLITE_RANGE) throw exceptions::range(error_code, sql); - else if(error_code == SQLITE_NOTADB) throw exceptions::notadb(error_code, sql); - else throw sqlite_exception(error_code, sql); - } - } + // std::optional support for NULL values + #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT + template + using optional = std::experimental::optional; + #else + template + using optional = std::optional; + #endif + #endif class database; class database_binder; @@ -139,39 +99,39 @@ namespace sqlite { _stmt(std::move(other._stmt)), _inx(other._inx), execution_started(other.execution_started) { } - void reset() { - sqlite3_reset(_stmt.get()); - sqlite3_clear_bindings(_stmt.get()); - _inx = 1; - used(false); - } - void execute() { + _start_execute(); int hresult; while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {} if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult, sql()); + errors::throw_sqlite_error(hresult, sql()); } - used(true); /* prevent from executing again when goes out of scope */ } - std::string sql() { + std::string sql() { #if SQLITE_VERSION_NUMBER >= 3014000 - auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; - std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); - return str ? str.get() : original_sql(); + auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; + std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); + return str ? str.get() : original_sql(); #else - return original_sql(); + return original_sql(); #endif - } + } - std::string original_sql() { - return sqlite3_sql(_stmt.get()); - } + std::string original_sql() { + return sqlite3_sql(_stmt.get()); + } - void used(bool state) { execution_started = state; } + void used(bool state) { + if(!state) { + // We may have to reset first if we haven't done so already: + _next_index(); + --_inx; + } + execution_started = state; + } bool used() const { return execution_started; } private: @@ -183,53 +143,63 @@ namespace sqlite { bool execution_started = false; + int _next_index() { + if(execution_started && !_inx) { + sqlite3_reset(_stmt.get()); + sqlite3_clear_bindings(_stmt.get()); + } + return ++_inx; + } + void _start_execute() { + _next_index(); + _inx = 0; + used(true); + } + void _extract(std::function call_back) { - execution_started = true; int hresult; + _start_execute(); while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { call_back(); } if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult, sql()); + errors::throw_sqlite_error(hresult, sql()); } - reset(); } void _extract_single_value(std::function call_back) { - execution_started = true; int hresult; + _start_execute(); if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { call_back(); } else if(hresult == SQLITE_DONE) { - throw exceptions::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE); + throw errors::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE); } if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { - throw exceptions::more_rows("not all rows extracted", sql(), SQLITE_ROW); + throw errors::more_rows("not all rows extracted", sql(), SQLITE_ROW); } if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult, sql()); + errors::throw_sqlite_error(hresult, sql()); } - reset(); } sqlite3_stmt* _prepare(const std::u16string& sql) { - int hresult; - sqlite3_stmt* tmp = nullptr; - hresult = sqlite3_prepare16_v2(_db.get(), sql.data(), -1, &tmp, nullptr); - if((hresult) != SQLITE_OK) exceptions::throw_sqlite_error(hresult); - return tmp; + return _prepare(utility::utf16_to_utf8(sql)); } sqlite3_stmt* _prepare(const std::string& sql) { int hresult; sqlite3_stmt* tmp = nullptr; - hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, nullptr); - if((hresult) != SQLITE_OK) exceptions::throw_sqlite_error(hresult, sql); + const char *remaining; + hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, &remaining); + if(hresult != SQLITE_OK) errors::throw_sqlite_error(hresult, sql); + if(!std::all_of(remaining, sql.data() + sql.size(), [](char ch) {return std::isspace(ch);})) + throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql); return tmp; } @@ -249,10 +219,15 @@ namespace sqlite { || std::is_integral::value || std::is_same::value > { }; +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT + template + struct is_sqlite_value< std::variant > : public std::integral_constant< + bool, + true + > { }; +#endif - template friend database_binder& operator <<(database_binder& db, const T& val); - template friend void get_col_from_db(database_binder& db, int inx, T& val); /* for vector support */ template friend database_binder& operator <<(database_binder& db, const std::vector& val); template friend void get_col_from_db(database_binder& db, int inx, std::vector& val); @@ -260,11 +235,15 @@ namespace sqlite { friend database_binder& operator <<(database_binder& db, std::nullptr_t); template friend database_binder& operator <<(database_binder& db, const std::unique_ptr& val); template friend void get_col_from_db(database_binder& db, int inx, std::unique_ptr& val); +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT + template friend database_binder& operator <<(database_binder& db, const std::variant& val); + template friend void get_col_from_db(database_binder& db, int inx, std::variant& val); +#endif template friend T operator++(database_binder& db, int); // Overload instead of specializing function templates (http://www.gotw.ca/publications/mill17.htm) friend database_binder& operator<<(database_binder& db, const int& val); friend void get_col_from_db(database_binder& db, int inx, int& val); - friend database_binder& operator <<(database_binder& db, const sqlite_int64& val); + friend database_binder& operator <<(database_binder& db, const sqlite_int64& val); friend void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i); friend database_binder& operator <<(database_binder& db, const float& val); friend void get_col_from_db(database_binder& db, int inx, float& f); @@ -276,9 +255,9 @@ namespace sqlite { friend database_binder& operator <<(database_binder& db, const std::u16string& txt); -#ifdef _MODERN_SQLITE_STD_OPTIONAL_SUPPORT - template friend database_binder& operator <<(database_binder& db, const std::optional& val); - template friend void get_col_from_db(database_binder& db, int inx, std::optional& o); +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template friend database_binder& operator <<(database_binder& db, const optional& val); + template friend void get_col_from_db(database_binder& db, int inx, optional& o); #endif #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT @@ -291,19 +270,19 @@ namespace sqlite { database_binder(std::shared_ptr db, std::u16string const & sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), - _inx(1) { + _inx(0) { } database_binder(std::shared_ptr db, std::string const & sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), - _inx(1) { + _inx(0) { } ~database_binder() noexcept(false) { /* Will be executed if no >>op is found, but not if an exception is in mid flight */ - if(!execution_started && !_has_uncaught_exception && _stmt) { + if(!used() && !_has_uncaught_exception && _stmt) { execute(); } } @@ -401,7 +380,28 @@ namespace sqlite { ); } + enum class OpenFlags { + READONLY = SQLITE_OPEN_READONLY, + READWRITE = SQLITE_OPEN_READWRITE, + CREATE = SQLITE_OPEN_CREATE, + NOMUTEX = SQLITE_OPEN_NOMUTEX, + FULLMUTEX = SQLITE_OPEN_FULLMUTEX, + SHAREDCACHE = SQLITE_OPEN_SHAREDCACHE, + PRIVATECACH = SQLITE_OPEN_PRIVATECACHE, + URI = SQLITE_OPEN_URI + }; + inline OpenFlags operator|(const OpenFlags& a, const OpenFlags& b) { + return static_cast(static_cast(a) | static_cast(b)); + } + enum class Encoding { + ANY = SQLITE_ANY, + UTF8 = SQLITE_UTF8, + UTF16 = SQLITE_UTF16 + }; struct sqlite_config { + OpenFlags flags = OpenFlags::READWRITE | OpenFlags::CREATE; + const char *zVfs = nullptr; + Encoding encoding = Encoding::ANY; }; class database { @@ -409,33 +409,30 @@ namespace sqlite { std::shared_ptr _db; public: - database(std::u16string const & db_name): _db(nullptr) { + database(const std::string &db_name, const sqlite_config &config = {}): _db(nullptr) { sqlite3* tmp = nullptr; - auto ret = sqlite3_open16(db_name.data(), &tmp); + auto ret = sqlite3_open_v2(db_name.data(), &tmp, static_cast(config.flags), config.zVfs); _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. - if(ret != SQLITE_OK) exceptions::throw_sqlite_error(ret); - //_db.reset(tmp, sqlite3_close); // alternative close. (faster?) + if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret); + sqlite3_extended_result_codes(_db.get(), true); + if(config.encoding == Encoding::UTF16) + *this << R"(PRAGMA encoding = "UTF-16";)"; } - database(std::string const & db_name): _db(nullptr) { + database(const std::u16string &db_name, const sqlite_config &config = {}): _db(nullptr) { + auto db_name_utf8 = utility::utf16_to_utf8(db_name); sqlite3* tmp = nullptr; - auto ret = sqlite3_open(db_name.data(), &tmp); + auto ret = sqlite3_open_v2(db_name_utf8.data(), &tmp, static_cast(config.flags), config.zVfs); _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. - if(ret != SQLITE_OK) exceptions::throw_sqlite_error(ret); - //_db.reset(tmp, sqlite3_close); // alternative close. (faster?) + if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret); + sqlite3_extended_result_codes(_db.get(), true); + if(config.encoding != Encoding::UTF8) + *this << R"(PRAGMA encoding = "UTF-16";)"; } database(std::shared_ptr db): _db(db) {} - database(const std::string &db_name, const sqlite_config &config): database(db_name) { - (void)config; // Suppress unused warning - } - - database(const std::u16string &db_name, const sqlite_config &config): database(db_name) { - (void)config; // Suppress unused warning - } - database_binder operator<<(const std::string& sql) { return database_binder(_db, sql); } @@ -469,7 +466,7 @@ namespace sqlite { nullptr, nullptr, [](void* ptr){ delete static_cast(ptr); })) - exceptions::throw_sqlite_error(result); + errors::throw_sqlite_error(result); } template @@ -485,7 +482,7 @@ namespace sqlite { [](void* ptr){ delete static_cast(ptr); })) - exceptions::throw_sqlite_error(result); + errors::throw_sqlite_error(result); } }; @@ -541,10 +538,9 @@ namespace sqlite { // int inline database_binder& operator<<(database_binder& db, const int& val) { int hresult; - if((hresult = sqlite3_bind_int(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_int(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const int& val) { @@ -568,11 +564,10 @@ namespace sqlite { // sqlite_int64 inline database_binder& operator <<(database_binder& db, const sqlite_int64& val) { int hresult; - if((hresult = sqlite3_bind_int64(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_int64(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const sqlite_int64& val) { @@ -596,11 +591,10 @@ namespace sqlite { // float inline database_binder& operator <<(database_binder& db, const float& val) { int hresult; - if((hresult = sqlite3_bind_double(db._stmt.get(), db._inx, double(val))) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), double(val))) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const float& val) { @@ -624,11 +618,10 @@ namespace sqlite { // double inline database_binder& operator <<(database_binder& db, const double& val) { int hresult; - if((hresult = sqlite3_bind_double(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const double& val) { @@ -654,10 +647,9 @@ namespace sqlite { void const* buf = reinterpret_cast(vec.data()); int bytes = vec.size() * sizeof(T); int hresult; - if((hresult = sqlite3_bind_blob(db._stmt.get(), db._inx, buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_blob(db._stmt.get(), db._next_index(), buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } template inline void store_result_in_db(sqlite3_context* db, const std::vector& vec) { @@ -687,10 +679,9 @@ namespace sqlite { /* for nullptr support */ inline database_binder& operator <<(database_binder& db, std::nullptr_t) { int hresult; - if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_null(db._stmt.get(), db._next_index())) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, std::nullptr_t) { @@ -749,11 +740,10 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const std::string& txt) { int hresult; - if((hresult = sqlite3_bind_text(db._stmt.get(), db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_text(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const std::string& val) { @@ -780,49 +770,74 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const std::u16string& txt) { int hresult; - if((hresult = sqlite3_bind_text16(db._stmt.get(), db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + if((hresult = sqlite3_bind_text16(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { + errors::throw_sqlite_error(hresult, db.sql()); } - ++db._inx; return db; } inline void store_result_in_db(sqlite3_context* db, const std::u16string& val) { sqlite3_result_text16(db, val.data(), -1, SQLITE_TRANSIENT); } + + // Other integer types + template::value>::type> + inline database_binder& operator <<(database_binder& db, const Integral& val) { + return db << static_cast(val); + } + template::type>> + inline void store_result_in_db(sqlite3_context* db, const Integral& val) { + store_result_in_db(db, static_cast(val)); + } + template::value>::type> + inline void get_col_from_db(database_binder& db, int inx, Integral& val) { + sqlite3_int64 i; + get_col_from_db(db, inx, i); + val = i; + } + template::value>::type> + inline void get_val_from_db(sqlite3_value *value, Integral& val) { + sqlite3_int64 i; + get_val_from_db(value, i); + val = i; + } + // std::optional support for NULL values -#ifdef _MODERN_SQLITE_STD_OPTIONAL_SUPPORT - template inline database_binder& operator <<(database_binder& db, const std::optional& val) { +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template inline database_binder& operator <<(database_binder& db, const optional& val) { if(val) { - return operator << (std::move(db), std::move(*val)); - } - int hresult; - if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + return db << std::move(*val); + } else { + return db << nullptr; } - - ++db._inx; - return db; } - template inline void store_result_in_db(sqlite3_context* db, const std::optional& val) { + template inline void store_result_in_db(sqlite3_context* db, const optional& val) { if(val) { store_result_in_db(db, *val); } sqlite3_result_null(db); } - template inline void get_col_from_db(database_binder& db, int inx, std::optional& o) { + template inline void get_col_from_db(database_binder& db, int inx, optional& o) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT + o = std::experimental::nullopt; + #else o.reset(); + #endif } else { OptionalT v; - get_col_from_db(value, v); + get_col_from_db(db, inx, v); o = std::move(v); } } - template inline void get_val_from_db(sqlite3_value *value, std::optional& o) { + template inline void get_val_from_db(sqlite3_value *value, optional& o) { if(sqlite3_value_type(value) == SQLITE_NULL) { + #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT + o = std::experimental::nullopt; + #else o.reset(); + #endif } else { OptionalT v; get_val_from_db(value, v); @@ -835,15 +850,10 @@ namespace sqlite { #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT template inline database_binder& operator <<(database_binder& db, const boost::optional& val) { if(val) { - return operator << (std::move(db), std::move(*val)); - } - int hresult; - if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult, db.sql()); + return db << std::move(*val); + } else { + return db << nullptr; } - - ++db._inx; - return db; } template inline void store_result_in_db(sqlite3_context* db, const boost::optional& val) { if(val) { @@ -872,11 +882,33 @@ namespace sqlite { } #endif +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT + template inline database_binder& operator <<(database_binder& db, const std::variant& val) { + std::visit([&](auto &&opt) {db << std::forward(opt);}, val); + return db; + } + template inline void store_result_in_db(sqlite3_context* db, const std::variant& val) { + std::visit([&](auto &&opt) {store_result_in_db(db, std::forward(opt));}, val); + } + template inline void get_col_from_db(database_binder& db, int inx, std::variant& val) { + utility::variant_select(sqlite3_column_type(db._stmt.get(), inx))([&](auto v) { + get_col_from_db(db, inx, v); + val = std::move(v); + }); + } + template inline void get_val_from_db(sqlite3_value *value, std::variant& val) { + utility::variant_select(sqlite3_value_type(value))([&](auto v) { + get_val_from_db(value, v); + val = std::move(v); + }); + } +#endif + // Some ppl are lazy so we have a operator for proper prep. statemant handling. - void inline operator++(database_binder& db, int) { db.execute(); db.reset(); } + void inline operator++(database_binder& db, int) { db.execute(); } // Convert the rValue binder to a reference and call first op<<, its needed for the call that creates the binder (be carefull of recursion here!) - template database_binder& operator << (database_binder&& db, const T& val) { return db << val; } + template database_binder&& operator << (database_binder&& db, const T& val) { db << val; return std::move(db); } namespace sql_function_binder { template @@ -897,8 +929,20 @@ namespace sqlite { ) { auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); if(!ctxt) return; - if(!ctxt->constructed) new(ctxt) AggregateCtxt(); - step(db, count, vals, ctxt->obj); + try { + if(!ctxt->constructed) new(ctxt) AggregateCtxt(); + step(db, count, vals, ctxt->obj); + return; + } catch(sqlite_exception &e) { + sqlite3_result_error_code(db, e.get_code()); + sqlite3_result_error(db, e.what(), -1); + } catch(std::exception &e) { + sqlite3_result_error(db, e.what(), -1); + } catch(...) { + sqlite3_result_error(db, "Unknown error", -1); + } + if(ctxt && ctxt->constructed) + ctxt->~AggregateCtxt(); } template< @@ -936,15 +980,15 @@ namespace sqlite { Values&&... values ) { static_cast(sqlite3_user_data(db))->first(std::forward(values)...); - }; + } template< typename ContextType, typename Functions > inline void final(sqlite3_context* db) { + auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); try { - auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); if(!ctxt) return; if(!ctxt->constructed) new(ctxt) AggregateCtxt(); store_result_in_db(db, @@ -957,6 +1001,8 @@ namespace sqlite { } catch(...) { sqlite3_result_error(db, "Unknown error", -1); } + if(ctxt && ctxt->constructed) + ctxt->~AggregateCtxt(); } template< diff --git a/hdr/sqlite_modern_cpp/errors.h b/hdr/sqlite_modern_cpp/errors.h new file mode 100644 index 00000000..2b9ab75d --- /dev/null +++ b/hdr/sqlite_modern_cpp/errors.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include + +namespace sqlite { + + class sqlite_exception: public std::runtime_error { + public: + sqlite_exception(const char* msg, std::string sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} + sqlite_exception(int code, std::string sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {} + int get_code() const {return code & 0xFF;} + int get_extended_code() const {return code;} + std::string get_sql() const {return sql;} + private: + int code; + std::string sql; + }; + + namespace errors { + //One more or less trivial derived error class for each SQLITE error. + //Note the following are not errors so have no classes: + //SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE + // + //Note these names are exact matches to the names of the SQLITE error codes. +#define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ + class name: public sqlite_exception { using sqlite_exception::sqlite_exception; };\ + derived +#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ + class base ## _ ## sub: public base { using base::base; }; +#include "lists/error_codes.h" +#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED +#undef SQLITE_MODERN_CPP_ERROR_CODE + + //Some additional errors are here for the C++ interface + class more_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class no_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class more_statements: public sqlite_exception { using sqlite_exception::sqlite_exception; }; // Prepared statements can only contain one statement + class invalid_utf16: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + + static void throw_sqlite_error(const int& error_code, const std::string &sql = "") { + switch(error_code & 0xFF) { +#define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ + case SQLITE_ ## NAME: switch(error_code) { \ + derived \ + default: throw name(error_code, sql); \ + } +#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ + case SQLITE_ ## BASE ## _ ## SUB: throw base ## _ ## sub(error_code, sql); +#include "lists/error_codes.h" +#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED +#undef SQLITE_MODERN_CPP_ERROR_CODE + default: throw sqlite_exception(error_code, sql); + } + } + } + namespace exceptions = errors; +} diff --git a/hdr/sqlite_modern_cpp/lists/error_codes.h b/hdr/sqlite_modern_cpp/lists/error_codes.h new file mode 100644 index 00000000..5dfa0d37 --- /dev/null +++ b/hdr/sqlite_modern_cpp/lists/error_codes.h @@ -0,0 +1,93 @@ +#if SQLITE_VERSION_NUMBER < 3010000 +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) +#endif +SQLITE_MODERN_CPP_ERROR_CODE(ERROR,error,) +SQLITE_MODERN_CPP_ERROR_CODE(INTERNAL,internal,) +SQLITE_MODERN_CPP_ERROR_CODE(PERM,perm,) +SQLITE_MODERN_CPP_ERROR_CODE(ABORT,abort, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(ABORT,ROLLBACK,abort,rollback) +) +SQLITE_MODERN_CPP_ERROR_CODE(BUSY,busy, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BUSY,RECOVERY,busy,recovery) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BUSY,SNAPSHOT,busy,snapshot) +) +SQLITE_MODERN_CPP_ERROR_CODE(LOCKED,locked, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(LOCKED,SHAREDCACHE,locked,sharedcache) +) +SQLITE_MODERN_CPP_ERROR_CODE(NOMEM,nomem,) +SQLITE_MODERN_CPP_ERROR_CODE(READONLY,readonly,) +SQLITE_MODERN_CPP_ERROR_CODE(INTERRUPT,interrupt,) +SQLITE_MODERN_CPP_ERROR_CODE(IOERR,ioerr, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,READ,ioerr,read) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHORT_READ,ioerr,short_read) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,WRITE,ioerr,write) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,FSYNC,ioerr,fsync) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DIR_FSYNC,ioerr,dir_fsync) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,TRUNCATE,ioerr,truncate) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,FSTAT,ioerr,fstat) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,UNLOCK,ioerr,unlock) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,RDLOCK,ioerr,rdlock) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DELETE,ioerr,delete) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,BLOCKED,ioerr,blocked) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,NOMEM,ioerr,nomem) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,ACCESS,ioerr,access) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CHECKRESERVEDLOCK,ioerr,checkreservedlock) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,LOCK,ioerr,lock) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CLOSE,ioerr,close) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DIR_CLOSE,ioerr,dir_close) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMOPEN,ioerr,shmopen) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMSIZE,ioerr,shmsize) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMLOCK,ioerr,shmlock) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMMAP,ioerr,shmmap) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SEEK,ioerr,seek) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DELETE_NOENT,ioerr,delete_noent) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,MMAP,ioerr,mmap) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,GETTEMPPATH,ioerr,gettemppath) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CONVPATH,ioerr,convpath) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,VNODE,ioerr,vnode) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,AUTH,ioerr,auth) +) +SQLITE_MODERN_CPP_ERROR_CODE(CORRUPT,corrupt, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CORRUPT,VTAB,corrupt,vtab) +) +SQLITE_MODERN_CPP_ERROR_CODE(NOTFOUND,notfound,) +SQLITE_MODERN_CPP_ERROR_CODE(FULL,full,) +SQLITE_MODERN_CPP_ERROR_CODE(CANTOPEN,cantopen, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,NOTEMPDIR,cantopen,notempdir) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,ISDIR,cantopen,isdir) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,FULLPATH,cantopen,fullpath) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,CONVPATH,cantopen,convpath) +) +SQLITE_MODERN_CPP_ERROR_CODE(PROTOCOL,protocol,) +SQLITE_MODERN_CPP_ERROR_CODE(EMPTY,empty,) +SQLITE_MODERN_CPP_ERROR_CODE(SCHEMA,schema,) +SQLITE_MODERN_CPP_ERROR_CODE(TOOBIG,toobig,) +SQLITE_MODERN_CPP_ERROR_CODE(CONSTRAINT,constraint, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,CHECK,constraint,check) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,COMMITHOOK,constraint,commithook) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,FOREIGNKEY,constraint,foreignkey) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,FUNCTION,constraint,function) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,NOTNULL,constraint,notnull) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,PRIMARYKEY,constraint,primarykey) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,TRIGGER,constraint,trigger) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,UNIQUE,constraint,unique) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,VTAB,constraint,vtab) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,ROWID,constraint,rowid) +) +SQLITE_MODERN_CPP_ERROR_CODE(MISMATCH,mismatch,) +SQLITE_MODERN_CPP_ERROR_CODE(MISUSE,misuse,) +SQLITE_MODERN_CPP_ERROR_CODE(NOLFS,nolfs,) +SQLITE_MODERN_CPP_ERROR_CODE(AUTH,auth, +) +SQLITE_MODERN_CPP_ERROR_CODE(FORMAT,format,) +SQLITE_MODERN_CPP_ERROR_CODE(RANGE,range,) +SQLITE_MODERN_CPP_ERROR_CODE(NOTADB,notadb,) +SQLITE_MODERN_CPP_ERROR_CODE(NOTICE,notice, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(NOTICE,RECOVER_WAL,notice,recover_wal) + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(NOTICE,RECOVER_ROLLBACK,notice,recover_rollback) +) +SQLITE_MODERN_CPP_ERROR_CODE(WARNING,warning, + SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(WARNING,AUTOINDEX,warning,autoindex) +) diff --git a/hdr/sqlite_modern_cpp/log.h b/hdr/sqlite_modern_cpp/log.h new file mode 100644 index 00000000..a8f7be22 --- /dev/null +++ b/hdr/sqlite_modern_cpp/log.h @@ -0,0 +1,101 @@ +#include "errors.h" + +#include + +#include +#include +#include + +namespace sqlite { + namespace detail { + template + using void_t = void; + template + struct is_callable : std::false_type {}; + template + struct is_callable()(std::declval()...))>> : std::true_type {}; + template + class FunctorOverload: public Functor, public FunctorOverload { + public: + template + FunctorOverload(Functor1 &&functor, Remaining &&... remaining): + Functor(std::forward(functor)), + FunctorOverload(std::forward(remaining)...) {} + using Functor::operator(); + using FunctorOverload::operator(); + }; + template + class FunctorOverload: public Functor { + public: + template + FunctorOverload(Functor1 &&functor): + Functor(std::forward(functor)) {} + using Functor::operator(); + }; + template + class WrapIntoFunctor: public Functor { + public: + template + WrapIntoFunctor(Functor1 &&functor): + Functor(std::forward(functor)) {} + using Functor::operator(); + }; + template + class WrapIntoFunctor { + ReturnType(*ptr)(Arguments...); + public: + WrapIntoFunctor(ReturnType(*ptr)(Arguments...)): ptr(ptr) {} + ReturnType operator()(Arguments... arguments) { return (*ptr)(std::forward(arguments)...); } + }; + inline void store_error_log_data_pointer(std::shared_ptr ptr) { + static std::shared_ptr stored; + stored = std::move(ptr); + } + template + std::shared_ptr::type> make_shared_inferred(T &&t) { + return std::make_shared::type>(std::forward(t)); + } + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler); + template + typename std::enable_if::value>::type + error_log(Handler &&handler); + template + typename std::enable_if=2>::type + error_log(Handler &&...handler) { + return error_log(detail::FunctorOverload::type>...>(std::forward(handler)...)); + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler) { + return error_log(std::forward(handler), [](const sqlite_exception&) {}); + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler) { + auto ptr = detail::make_shared_inferred([handler = std::forward(handler)](int error_code, const char *errstr) mutable { + switch(error_code & 0xFF) { +#define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ + case SQLITE_ ## NAME: switch(error_code) { \ + derived \ + default: handler(errors::name(errstr, "", error_code)); \ + };break; +#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ + case SQLITE_ ## BASE ## _ ## SUB: \ + handler(errors::base ## _ ## sub(errstr, "", error_code)); \ + break; +#include "lists/error_codes.h" +#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED +#undef SQLITE_MODERN_CPP_ERROR_CODE + default: handler(sqlite_exception(errstr, "", error_code)); \ + } + }); + + sqlite3_config(SQLITE_CONFIG_LOG, (void(*)(void*,int,const char*))[](void *functor, int error_code, const char *errstr) { + (*static_cast(functor))(error_code, errstr); + }, ptr.get()); + detail::store_error_log_data_pointer(std::move(ptr)); + } +} diff --git a/hdr/sqlite_modern_cpp/sqlcipher.h b/hdr/sqlite_modern_cpp/sqlcipher.h index 6d2c3d88..da0f0189 100644 --- a/hdr/sqlite_modern_cpp/sqlcipher.h +++ b/hdr/sqlite_modern_cpp/sqlcipher.h @@ -23,22 +23,22 @@ namespace sqlite { void set_key(const std::string &key) { if(auto ret = sqlite3_key(_db.get(), key.data(), key.size())) - exceptions::throw_sqlite_error(ret); + errors::throw_sqlite_error(ret); } void set_key(const std::string &key, const std::string &db_name) { if(auto ret = sqlite3_key_v2(_db.get(), db_name.c_str(), key.data(), key.size())) - exceptions::throw_sqlite_error(ret); + errors::throw_sqlite_error(ret); } void rekey(const std::string &new_key) { if(auto ret = sqlite3_rekey(_db.get(), new_key.data(), new_key.size())) - exceptions::throw_sqlite_error(ret); + errors::throw_sqlite_error(ret); } void rekey(const std::string &new_key, const std::string &db_name) { if(auto ret = sqlite3_rekey_v2(_db.get(), db_name.c_str(), new_key.data(), new_key.size())) - exceptions::throw_sqlite_error(ret); + errors::throw_sqlite_error(ret); } }; } diff --git a/hdr/sqlite_modern_cpp/utility/function_traits.h b/hdr/sqlite_modern_cpp/utility/function_traits.h index 9decc42c..cd8fab09 100644 --- a/hdr/sqlite_modern_cpp/utility/function_traits.h +++ b/hdr/sqlite_modern_cpp/utility/function_traits.h @@ -20,17 +20,7 @@ namespace sqlite { > struct function_traits< ReturnType(ClassType::*)(Arguments...) const - > { - typedef ReturnType result_type; - - template - using argument = typename std::tuple_element< - Index, - std::tuple - >::type; - - static const std::size_t arity = sizeof...(Arguments); - }; + > : function_traits { }; /* support the non-const operator () * this will work with user defined functors */ @@ -41,6 +31,14 @@ namespace sqlite { > struct function_traits< ReturnType(ClassType::*)(Arguments...) + > : function_traits { }; + + template < + typename ReturnType, + typename... Arguments + > + struct function_traits< + ReturnType(*)(Arguments...) > { typedef ReturnType result_type; diff --git a/hdr/sqlite_modern_cpp/utility/utf16_utf8.h b/hdr/sqlite_modern_cpp/utility/utf16_utf8.h new file mode 100644 index 00000000..ea21723f --- /dev/null +++ b/hdr/sqlite_modern_cpp/utility/utf16_utf8.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "../errors.h" + +namespace sqlite { + namespace utility { + inline std::string utf16_to_utf8(const std::u16string &input) { + struct : std::codecvt { + } codecvt; + std::mbstate_t state{}; + std::string result((std::max)(input.size() * 3 / 2, std::size_t(4)), '\0'); + const char16_t *remaining_input = input.data(); + std::size_t produced_output = 0; + while(true) { + char *used_output; + switch(codecvt.out(state, remaining_input, &input[input.size()], + remaining_input, &result[produced_output], + &result[result.size() - 1] + 1, used_output)) { + case std::codecvt_base::ok: + result.resize(used_output - result.data()); + return result; + case std::codecvt_base::noconv: + // This should be unreachable + case std::codecvt_base::error: + throw errors::invalid_utf16("Invalid UTF-16 input", ""); + case std::codecvt_base::partial: + if(used_output == result.data() + produced_output) + throw errors::invalid_utf16("Unexpected end of input", ""); + produced_output = used_output - result.data(); + result.resize( + result.size() + + (std::max)((&input[input.size()] - remaining_input) * 3 / 2, + std::ptrdiff_t(4))); + } + } + } + } // namespace utility +} // namespace sqlite diff --git a/hdr/sqlite_modern_cpp/utility/variant.h b/hdr/sqlite_modern_cpp/utility/variant.h new file mode 100644 index 00000000..11a8429f --- /dev/null +++ b/hdr/sqlite_modern_cpp/utility/variant.h @@ -0,0 +1,201 @@ +#pragma once + +#include "../errors.h" +#include +#include +#include + +namespace sqlite::utility { + template + struct VariantFirstNullable { + using type = void; + }; + template + struct VariantFirstNullable { + using type = typename VariantFirstNullable::type; + }; +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct VariantFirstNullable, Options...> { + using type = std::optional; + }; +#endif + template + struct VariantFirstNullable, Options...> { + using type = std::unique_ptr; + }; + template + struct VariantFirstNullable { + using type = std::nullptr_t; + }; + template + inline void variant_select_null(Callback&&callback) { + if constexpr(std::is_same_v::type, void>) { + throw errors::mismatch("NULL is unsupported by this variant.", "", SQLITE_MISMATCH); + } else { + std::forward(callback)(typename VariantFirstNullable::type()); + } + } + + template + struct VariantFirstIntegerable { + using type = void; + }; + template + struct VariantFirstIntegerable { + using type = typename VariantFirstIntegerable::type; + }; +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct VariantFirstIntegerable, Options...> { + using type = std::conditional_t::type, T>, std::optional, typename VariantFirstIntegerable::type>; + }; +#endif + template + struct VariantFirstIntegerable::type, T>>, std::unique_ptr, Options...> { + using type = std::conditional_t::type, T>, std::unique_ptr, typename VariantFirstIntegerable::type>; + }; + template + struct VariantFirstIntegerable { + using type = int; + }; + template + struct VariantFirstIntegerable { + using type = sqlite_int64; + }; + template + inline auto variant_select_integer(Callback&&callback) { + if constexpr(std::is_same_v::type, void>) { + throw errors::mismatch("Integer is unsupported by this variant.", "", SQLITE_MISMATCH); + } else { + std::forward(callback)(typename VariantFirstIntegerable::type()); + } + } + + template + struct VariantFirstFloatable { + using type = void; + }; + template + struct VariantFirstFloatable { + using type = typename VariantFirstFloatable::type; + }; +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct VariantFirstFloatable, Options...> { + using type = std::conditional_t::type, T>, std::optional, typename VariantFirstFloatable::type>; + }; +#endif + template + struct VariantFirstFloatable, Options...> { + using type = std::conditional_t::type, T>, std::unique_ptr, typename VariantFirstFloatable::type>; + }; + template + struct VariantFirstFloatable { + using type = float; + }; + template + struct VariantFirstFloatable { + using type = double; + }; + template + inline auto variant_select_float(Callback&&callback) { + if constexpr(std::is_same_v::type, void>) { + throw errors::mismatch("Real is unsupported by this variant.", "", SQLITE_MISMATCH); + } else { + std::forward(callback)(typename VariantFirstFloatable::type()); + } + } + + template + struct VariantFirstTextable { + using type = void; + }; + template + struct VariantFirstTextable { + using type = typename VariantFirstTextable::type; + }; +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct VariantFirstTextable, Options...> { + using type = std::conditional_t::type, T>, std::optional, typename VariantFirstTextable::type>; + }; +#endif + template + struct VariantFirstTextable, Options...> { + using type = std::conditional_t::type, T>, std::unique_ptr, typename VariantFirstTextable::type>; + }; + template + struct VariantFirstTextable { + using type = std::string; + }; + template + struct VariantFirstTextable { + using type = std::u16string; + }; + template + inline void variant_select_text(Callback&&callback) { + if constexpr(std::is_same_v::type, void>) { + throw errors::mismatch("Text is unsupported by this variant.", "", SQLITE_MISMATCH); + } else { + std::forward(callback)(typename VariantFirstTextable::type()); + } + } + + template + struct VariantFirstBlobable { + using type = void; + }; + template + struct VariantFirstBlobable { + using type = typename VariantFirstBlobable::type; + }; +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct VariantFirstBlobable, Options...> { + using type = std::conditional_t::type, T>, std::optional, typename VariantFirstBlobable::type>; + }; +#endif + template + struct VariantFirstBlobable, Options...> { + using type = std::conditional_t::type, T>, std::unique_ptr, typename VariantFirstBlobable::type>; + }; + template + struct VariantFirstBlobable>, std::vector, Options...> { + using type = std::vector; + }; + template + inline auto variant_select_blob(Callback&&callback) { + if constexpr(std::is_same_v::type, void>) { + throw errors::mismatch("Blob is unsupported by this variant.", "", SQLITE_MISMATCH); + } else { + std::forward(callback)(typename VariantFirstBlobable::type()); + } + } + + template + inline auto variant_select(int type) { + return [type](auto &&callback) { + using Callback = decltype(callback); + switch(type) { + case SQLITE_NULL: + variant_select_null(std::forward(callback)); + break; + case SQLITE_INTEGER: + variant_select_integer(std::forward(callback)); + break; + case SQLITE_FLOAT: + variant_select_float(std::forward(callback)); + break; + case SQLITE_TEXT: + variant_select_text(std::forward(callback)); + break; + case SQLITE_BLOB: + variant_select_blob(std::forward(callback)); + break; + default:; + /* assert(false); */ + } + }; + } +} diff --git a/sqlite_modern_cpp.pc b/sqlite_modern_cpp.pc deleted file mode 100644 index bbcc4458..00000000 --- a/sqlite_modern_cpp.pc +++ /dev/null @@ -1,8 +0,0 @@ -prefix=/usr/local -includedir=${prefix}/include - -Name: sqlite_modern_cpp -Description: Modern C++ interface to sqlite3 -Version: version-0.7 -Libs: -lsqlite3 -Cflags: -I${includedir} diff --git a/tests/blob_example.cc b/tests/blob_example.cc index c89f5d6a..3436c0d2 100644 --- a/tests/blob_example.cc +++ b/tests/blob_example.cc @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/tests/boost_optional.cc b/tests/boost_optional.cc index 1d05540d..651a4f26 100644 --- a/tests/boost_optional.cc +++ b/tests/boost_optional.cc @@ -1,4 +1,5 @@ #include +#include #define _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT #include @@ -25,27 +26,10 @@ void select(database& db, bool should_be_null) { }; } -struct TmpFile { - string fname; - - TmpFile() { - char f[] = "/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() { - unlink(fname.c_str()); - } -}; - int main() { try { // creates a database file 'dbfile.db' if it does not exists. - TmpFile file; - database db(file.fname); + database db(":memory:"); db << "drop table if exists test"; db << diff --git a/tests/error_log.cc b/tests/error_log.cc new file mode 100644 index 00000000..d59cf08c --- /dev/null +++ b/tests/error_log.cc @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +int main() { + bool error_detected = false; + error_log( + [&](errors::constraint) { + cerr << "Wrong error detected!" << endl; + }, + [&](errors::constraint_primarykey e) { + cerr << e.get_code() << '/' << e.get_extended_code() << ": " << e.what() << endl; + error_detected = true; + } + ); + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + } catch (errors::constraint& e) { + } + + if(!error_detected) { + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/tests/error_log2.cc b/tests/error_log2.cc new file mode 100644 index 00000000..fc0a5bdc --- /dev/null +++ b/tests/error_log2.cc @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +int main() { + bool error_detected = false; + error_log( + [&](errors::constraint e) { + cerr << e.get_code() << '/' << e.get_extended_code() << ": " << e.what() << endl; + error_detected = true; + } + ); + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + } catch (errors::constraint& e) { + } + + if(!error_detected) { + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/tests/exceptions.cc b/tests/exceptions.cc index 47581246..c79b0f5b 100644 --- a/tests/exceptions.cc +++ b/tests/exceptions.cc @@ -17,7 +17,7 @@ int main() { db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; // inserting again to produce error db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; - } catch (sqlite_exception& e) { + } catch (errors::constraint& e) { cerr << e.get_code() << ": " << e.what() << " during " << quoted(e.get_sql()) << endl; expception_thrown = true; @@ -29,9 +29,6 @@ int main() { cerr << "Wrong statement failed\n"; exit(EXIT_FAILURE); } - } catch (...) { - cerr << "Ok, we have our excpetion thrown" << endl; - expception_thrown = true; } if(!expception_thrown) { diff --git a/tests/extended_exceptions.cc b/tests/extended_exceptions.cc new file mode 100644 index 00000000..937d02f1 --- /dev/null +++ b/tests/extended_exceptions.cc @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +int main() { + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + bool expception_thrown = false; + + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + } catch (errors::constraint_primarykey& e) { + cerr << e.get_code() << '/' << e.get_extended_code() << ": " << e.what() << " during " + << quoted(e.get_sql()) << endl; + expception_thrown = true; +#if SQLITE_VERSION_NUMBER >= 3014000 + if(e.get_sql() != "INSERT INTO person (id,name) VALUES (1,'jack')") { +#else + if(e.get_sql() != "INSERT INTO person (id,name) VALUES (?,?)") { +#endif + cerr << "Wrong statement failed\n"; + exit(EXIT_FAILURE); + } + } + + if(!expception_thrown) { + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/tests/flags.cc b/tests/flags.cc new file mode 100644 index 00000000..77cb03d2 --- /dev/null +++ b/tests/flags.cc @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct TmpFile +{ + string fname; + + TmpFile(): fname("./flags.db") { } + ~TmpFile() { remove(fname.c_str()); } +}; + +#if BYTE_ORDER == BIG_ENDIAN +#define OUR_UTF16 "UTF-16be" +#else +#define OUR_UTF16 "UTF-16le" +#endif + +int main() +{ + try + { + TmpFile file; + sqlite::sqlite_config cfg; + std::string enc; + { + database db(":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + if(enc != "UTF-8") { + cout << "Unexpected encoding on line " << __LINE__ << '\n'; + exit(EXIT_FAILURE); + } + } + { + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + if(enc != OUR_UTF16) { + cout << "Unexpected encoding on line " << __LINE__ << '\n'; + exit(EXIT_FAILURE); + } + } + { + cfg.encoding = Encoding::UTF8; + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + if(enc != "UTF-8") { + cout << "Unexpected encoding on line " << __LINE__ << '\n'; + exit(EXIT_FAILURE); + } + } + { + cfg.encoding = Encoding::UTF16; + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + if(enc != OUR_UTF16) { + cout << "Unexpected encoding on line " << __LINE__ << '\n'; + exit(EXIT_FAILURE); + } + } + { + database db(file.fname, cfg); + db << "PRAGMA encoding;" >> enc; + if(enc != OUR_UTF16) { + cout << "Unexpected encoding on line " << __LINE__ << '\n'; + exit(EXIT_FAILURE); + } + + db << "CREATE TABLE foo (a string);"; + db << "INSERT INTO foo VALUES (?)" << "hello"; + } + { + cfg.flags = sqlite::OpenFlags::READONLY; + database db(file.fname, cfg); + + string str; + db << "SELECT a FROM foo;" >> str; + + if(str != "hello") + { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + + try { + db << "INSERT INTO foo VALUES (?)" << "invalid"; + cout << "Unexpected success on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } catch(errors::readonly&) {} + } + } + catch(sqlite_exception e) + { + cout << "Unexpected error " << e.what() << endl; + exit(EXIT_FAILURE); + } + catch(...) + { + cout << "Unknown error\n"; + exit(EXIT_FAILURE); + } + + cout << "OK\n"; + exit(EXIT_SUCCESS); +} diff --git a/tests/functions.cc b/tests/functions.cc index f1fb073b..ef8b1e28 100644 --- a/tests/functions.cc +++ b/tests/functions.cc @@ -5,6 +5,9 @@ using namespace sqlite; using namespace std; +int add_integers(int i, int j) { + return i+j; +} int main() { try @@ -13,7 +16,7 @@ int main() db.define("my_new_concat", [](std::string i, std::string j) {return i+j;}); db.define("my_new_concat", [](std::string i, std::string j, std::string k) {return i+j+k;}); - db.define("add_integers", [](int i, int j) {return i+j;}); + db.define("add_integers", &add_integers); std::string test1, test3; int test2 = 0; db << "select my_new_concat('Hello ','world!')" >> test1; diff --git a/tests/mov_ctor.cc b/tests/mov_ctor.cc index 4867dc09..9b807e90 100644 --- a/tests/mov_ctor.cc +++ b/tests/mov_ctor.cc @@ -1,7 +1,6 @@ // Fixing https://github.com/aminroosta/sqlite_modern_cpp/issues/63 #include #include -#include #include #include using namespace sqlite; diff --git a/tests/prepared_statment.cc b/tests/prepared_statment.cc index 5ad896fa..8080f0ca 100644 --- a/tests/prepared_statment.cc +++ b/tests/prepared_statment.cc @@ -1,6 +1,5 @@ #include #include -#include #include using namespace sqlite; using namespace std; @@ -15,10 +14,10 @@ int main() { int test = 4; pps << test; // set a bound var - pps >> test; // execute and reset statment + pps >> test; // execute statement pps << 4; // bind a rvalue - pps >> test; // and execute again + pps++; // and execute pps << 8 >> test; @@ -76,6 +75,15 @@ int main() { (pps4 << test << test << test)++; } + { + auto prep = db << "select ?"; + + prep << 5; + prep.execute(); + prep << 6; + prep.execute(); + } + } catch(sqlite_exception e) { cout << "Unexpected error " << e.what() << endl; diff --git a/tests/shared_connection.cc b/tests/shared_connection.cc index a75c04e5..e8ba5cb5 100644 --- a/tests/shared_connection.cc +++ b/tests/shared_connection.cc @@ -1,32 +1,15 @@ #include +#include #include -#include #include using namespace sqlite; using namespace std; -struct TmpFile { - string fname; - - TmpFile() { - char f[] = "/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() { - unlink(fname.c_str()); - } -}; - int main() { try { - TmpFile file; - database db(file.fname); + database db(":memory:"); { diff --git a/tests/simple_examples.cc b/tests/simple_examples.cc index 6104b897..be2040c0 100644 --- a/tests/simple_examples.cc +++ b/tests/simple_examples.cc @@ -1,42 +1,22 @@ #include +#include #include -#include #include using namespace sqlite; using namespace std; -struct TmpFile -{ - string fname; - - TmpFile() - { - char f[]="/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() - { - unlink(fname.c_str()); - } -}; - int main() { try { - TmpFile file; - database db(file.fname); + database db(":memory:"); - db << "CREATE TABLE foo (a integer, b string);"; + db << "CREATE TABLE foo (a integer, b string);\n"; db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello"; db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world"; string str; - db << "SELECT b from FOO where a=?;" << 2 >> str; + db << "SELECT b from FOO where a=?;" << 2L >> str; if(str != "world") { @@ -45,7 +25,7 @@ int main() } std::string sql("select 1+1"); - int test = 0; + long test = 0; db << sql >> test; if(test != 2) exit(EXIT_FAILURE); diff --git a/tests/sqlcipher.cc b/tests/sqlcipher.cc index be95b5eb..eb1e259a 100644 --- a/tests/sqlcipher.cc +++ b/tests/sqlcipher.cc @@ -1,7 +1,7 @@ #ifdef ENABLE_SQLCIPHER_TESTS #include +#include #include -#include #include using namespace sqlite; using namespace std; @@ -10,19 +10,8 @@ struct TmpFile { string fname; - TmpFile() - { - char f[]="/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() - { - unlink(fname.c_str()); - } + TmpFile(): fname("./sqlcipher.db") { } + ~TmpFile() { remove(fname.c_str()); } }; int main() @@ -55,7 +44,7 @@ int main() cout << "Can open with wrong key"; exit(EXIT_FAILURE); - } catch(exceptions::notadb) { + } catch(errors::notadb) { // Expected, wrong key } { diff --git a/tests/std_optional.cc b/tests/std_optional.cc index 5d182626..b81ca27c 100644 --- a/tests/std_optional.cc +++ b/tests/std_optional.cc @@ -1,4 +1,3 @@ -#include #include #include @@ -6,10 +5,10 @@ using namespace sqlite; using namespace std; -#if __has_include() +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT void insert(database& db, bool is_null) { int id = 1; - std::optional val; + sqlite::optional val; if(!is_null) val = 5; db << "delete from test where id = 1"; @@ -17,7 +16,7 @@ void insert(database& db, bool is_null) { } void select(database& db, bool should_be_null) { - db << "select id,val from test" >> [&](long long, std::optional val) { + db << "select id,val from test" >> [&](long long, sqlite::optional val) { if(should_be_null) { if(val) exit(EXIT_FAILURE); } else { @@ -26,27 +25,9 @@ void select(database& db, bool should_be_null) { }; } -struct TmpFile { - string fname; - - TmpFile() { - char f[] = "/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() { - unlink(fname.c_str()); - } -}; - int main() { try { - // creates a database file 'dbfile.db' if it does not exists. - TmpFile file; - database db(file.fname); + database db(":memory:"); db << "drop table if exists test"; db << diff --git a/tests/trycatchblocks.cc b/tests/trycatchblocks.cc index ef33facb..1c7b8e0a 100644 --- a/tests/trycatchblocks.cc +++ b/tests/trycatchblocks.cc @@ -1,98 +1,84 @@ -#include -#include -#include -#include -#include - -using namespace sqlite; -using std::string; - -struct TmpFile -{ - string fname; - - TmpFile() - { - char f[]="/tmp/sqlite_modern_cpp_test_XXXXXX"; - int fid = mkstemp(f); - close(fid); - - fname = f; - } - - ~TmpFile() - { - unlink(fname.c_str()); - } -}; - - -class DBInterface { - database db; - -public: - DBInterface( const string& fileName ) : db( fileName ) - { - } - - void LogRequest( const string& username, const string& ip, const string& request ) - { - try { - auto timestamp = std::to_string( time( nullptr ) ); - - db << - "create table if not exists log_request (" - " _id integer primary key autoincrement not null," - " username text," - " timestamp text," - " ip text," - " request text" - ");"; - db << "INSERT INTO log_request (username, timestamp, ip, request) VALUES (?,?,?,?);" - << username - << timestamp - << ip - << request; - } catch ( const std::exception& e ) { - std::cout << e.what() << std::endl; - } - } - - bool TestData( void ) { - try { - string username, timestamp, ip, request; - - db << "select username, timestamp, ip, request from log_request where username = ?" - << "test" - >> std::tie(username, timestamp, ip, request); - - if ( username == "test" && ip == "127.0.0.1" && request == "hello world" ) { - return true; - } - } catch ( const std::exception& e ) { - std::cout << e.what() << std::endl; - } - - return false; - } -}; - -int main( void ) -{ - // -------------------------------------------------------------------------- - // -- Test if writing to disk works properly from within a catch block. - // -------------------------------------------------------------------------- - try { - throw "hello"; - } - catch ( ... ) { - TmpFile tmpF; - DBInterface interf( tmpF.fname ); - interf.LogRequest( "test", "127.0.0.1", "hello world" ); - if ( !interf.TestData() ) { - exit( EXIT_FAILURE ); - } - } - - exit( EXIT_SUCCESS ); -} \ No newline at end of file +#include +#include +#include +#include +#include + +using namespace sqlite; +using std::string; + +struct TmpFile { + string fname; + + TmpFile(): fname("./trycatchblocks.db") {} + ~TmpFile() { remove(fname.c_str()); } +}; + + +class DBInterface { + database db; + +public: + DBInterface( const string& fileName ) : db( fileName ) { } + + void LogRequest( const string& username, const string& ip, const string& request ) + { + try { + auto timestamp = std::to_string( time( nullptr ) ); + + db << + "create table if not exists log_request (" + " _id integer primary key autoincrement not null," + " username text," + " timestamp text," + " ip text," + " request text" + ");"; + db << "INSERT INTO log_request (username, timestamp, ip, request) VALUES (?,?,?,?);" + << username + << timestamp + << ip + << request; + } catch ( const std::exception& e ) { + std::cout << e.what() << std::endl; + } + } + + bool TestData( void ) { + try { + string username, timestamp, ip, request; + + db << "select username, timestamp, ip, request from log_request where username = ?" + << "test" + >> std::tie(username, timestamp, ip, request); + + if ( username == "test" && ip == "127.0.0.1" && request == "hello world" ) { + return true; + } + } catch ( const std::exception& e ) { + std::cout << e.what() << std::endl; + } + + return false; + } +}; + +int main( void ) +{ + // -------------------------------------------------------------------------- + // -- Test if writing to disk works properly from within a catch block. + // -------------------------------------------------------------------------- + try { + throw "hello"; + } + catch ( ... ) { + TmpFile tmpF; + DBInterface interf(tmpF.fname); + interf.LogRequest( "test", "127.0.0.1", "hello world" ); + if ( !interf.TestData() ) { + exit( EXIT_FAILURE ); + } + } + + exit( EXIT_SUCCESS ); +} diff --git a/tests/variant.cc b/tests/variant.cc new file mode 100644 index 00000000..916faaf9 --- /dev/null +++ b/tests/variant.cc @@ -0,0 +1,57 @@ +#include +#include +#include +using namespace sqlite; +using namespace std; + +int main() +{ +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT + try + { + database db(":memory:"); + + db << "CREATE TABLE foo (a);"; + std::variant> v; + v = 1; + db << "INSERT INTO foo VALUES (?)" << v; + v = "a"; + db << "INSERT INTO foo VALUES (?)" << v; + + db << "SELECT a FROM foo WHERE a=?;" << 1 >> v; + + if(v.index() != 1 || std::get<1>(v) != 1) + { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + + db << "SELECT NULL" >> v; + if(std::get<2>(v)) { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + + db << "SELECT 0.0" >> v; + if(!std::get<2>(v)) { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + } + catch(sqlite_exception e) + { + cout << "Unexpected error " << e.what() << endl; + exit(EXIT_FAILURE); + } + catch(...) + { + cout << "Unknown error\n"; + exit(EXIT_FAILURE); + } + + cout << "OK\n"; + exit(EXIT_SUCCESS); +#else + exit(42); +#endif +}