diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 77fe2b34..eb09ec23 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -95,5 +95,6 @@ if (LLAMA_CURL) endif () target_include_directories(${TARGET} PUBLIC .) +target_include_directories(${TARGET} PUBLIC ./sqlite_modern_cpp/hdr) target_compile_features (${TARGET} PUBLIC cxx_std_11) target_link_libraries (${TARGET} PRIVATE ${LLAMA_COMMON_EXTRA_LIBS} PUBLIC llama Threads::Threads) diff --git a/common/sqlite_modern_cpp/.gitignore b/common/sqlite_modern_cpp/.gitignore new file mode 100644 index 00000000..a1e26fa7 --- /dev/null +++ b/common/sqlite_modern_cpp/.gitignore @@ -0,0 +1,9 @@ + +.deps + +config.log + +config.status +compile_commands.json +build/ + diff --git a/common/sqlite_modern_cpp/.travis.yml b/common/sqlite_modern_cpp/.travis.yml new file mode 100644 index 00000000..00a58193 --- /dev/null +++ b/common/sqlite_modern_cpp/.travis.yml @@ -0,0 +1,26 @@ +language: cpp +sudo: required +dist: trusty + +cache: + apt: true + directories: + - /home/travis/.hunter/ + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - george-edison55-precise-backports + packages: + - g++-5 + - libsqlcipher-dev + - cmake + +before_install: + - export CXX="g++-5" CC="gcc-5" + +script: mkdir build && cd ./build && cmake .. && cmake --build . && ctest . + +# TODO: fix sqlcipher test +# script: mkdir build && cd ./build && cmake -DENABLE_SQLCIPHER_TESTS=ON .. && make && ./tests diff --git a/common/sqlite_modern_cpp/.ycm_extra_conf.py b/common/sqlite_modern_cpp/.ycm_extra_conf.py new file mode 100644 index 00000000..8a71764c --- /dev/null +++ b/common/sqlite_modern_cpp/.ycm_extra_conf.py @@ -0,0 +1,100 @@ +import os +import ycm_core + +from clang_helpers import PrepareClangFlags + +def DirectoryOfThisScript(): + return os.path.dirname(os.path.abspath(__file__)) + +# This is the single most important line in this script. Everything else is just nice to have but +# not strictly necessary. +compilation_database_folder = DirectoryOfThisScript() + +# This provides a safe fall-back if no compilation commands are available. You could also add a +# includes relative to your project directory, for example. +flags = [ + '-Wall', + '-std=c++14', + '-x', + 'c++', + '-isystem', '/usr/local/include', + '-isystem', '/usr/include', + '-I.', +] + +database = ycm_core.CompilationDatabase(compilation_database_folder) + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/common/sqlite_modern_cpp/CMakeLists.txt b/common/sqlite_modern_cpp/CMakeLists.txt new file mode 100644 index 00000000..f18d59b4 --- /dev/null +++ b/common/sqlite_modern_cpp/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.0) +option(ENABLE_SQLCIPHER_TESTS "enable sqlchipher test") + +# Creates the file compile_commands.json in the build directory. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +set(HUNTER_TLS_VERIFY ON) +include("cmake/HunterGate.cmake") +include("cmake/Catch.cmake") + +HunterGate( + URL "https://github.com/cpp-pm/hunter/archive/v0.24.15.tar.gz" + SHA1 "8010d63d5ae611c564889d5fe12d3cb7a45703ac" +) + +project(SqliteModernCpp) + +hunter_add_package(Catch) +hunter_add_package(sqlite3) + +find_package(Catch2 CONFIG REQUIRED) +find_package(sqlite3 CONFIG REQUIRED) + +set(TEST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) +file(GLOB TEST_SOURCES ${TEST_SOURCE_DIR}/*.cc) + +IF(NOT ENABLE_SQLCIPHER_TESTS) + list(REMOVE_ITEM TEST_SOURCES ${TEST_SOURCE_DIR}/sqlcipher.cc) +ENDIF(NOT ENABLE_SQLCIPHER_TESTS) + +enable_testing() + +add_library (sqlite_modern_cpp INTERFACE) +target_include_directories(sqlite_modern_cpp INTERFACE hdr/) + +add_executable(tests_runner ${TEST_SOURCES}) +target_include_directories(tests_runner INTERFACE ${SQLITE3_INCLUDE_DIRS}) +if(ENABLE_SQLCIPHER_TESTS) + target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3 -lsqlcipher) +else() + target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3) +endif() + +catch_discover_tests(tests_runner) +target_compile_options(tests_runner PUBLIC $<$:/Zc:__cplusplus> ) + +# Place the file in the source directory, permitting us to place a single configuration file for YCM there. +# YCM is the code-completion engine for (neo)vim https://github.com/Valloric/YouCompleteMe +IF(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json") + EXECUTE_PROCESS( COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_SOURCE_DIR}/compile_commands.json + ) +ENDIF() diff --git a/common/sqlite_modern_cpp/License.txt b/common/sqlite_modern_cpp/License.txt new file mode 100644 index 00000000..595b1d63 --- /dev/null +++ b/common/sqlite_modern_cpp/License.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 aminroosta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/sqlite_modern_cpp/README.md b/common/sqlite_modern_cpp/README.md new file mode 100644 index 00000000..cd94069a --- /dev/null +++ b/common/sqlite_modern_cpp/README.md @@ -0,0 +1,482 @@ +[![Build Status](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/status.png)](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/latest) + +sqlite modern cpp wrapper +==== + +This library is a lightweight modern wrapper around sqlite C api . + +```c++ +#include +#include +using namespace sqlite; +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. + // If you're using C++17, `std::string_view` and `std::u16string_view` can be used as string types. + // 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; + + // selects 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; + }; + + // a for loop can be used too: + // with named variables + for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 18) { + int age; string name; double weight; + row >> age >> name >> weight; + cout << age << ' ' << name << ' ' << weight << endl; + } + // or with a tuple + for(tuple row : db << "select age,name,weight from user where age > ? ;" << 18) { + cout << get<0>(row) << ' ' << get<1>(row) << ' ' << get<2>(row) << endl; + } + + // selects the count(*) from user table + // note that you can extract a single column single row result only to : int,long,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 (const exception& e) { + cout << e.what() << endl; + } +} +``` + +You can not execute multiple statements separated by semicolons in one go. + +Additional flags +---- +You can pass additional open flags to SQLite by using a config object: + +```c++ +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. +``` + +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. + +```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.execute(); +// or like this +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 + +// 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++; +} +``` + +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 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. +``` + +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 ... + +``` + +Blob +---- +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}; + +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; +}; +``` + +NULL values +---- +If you have databases where some rows may be null, you can use `std::unique_ptr` to retain the NULL values between C++ variables and the database. + +```c++ +db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);"; +db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector { 1, 2 , 3}; +unique_ptr ptr_null; // you can even bind empty unique_ptr +db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr; + +db << "select age,name,img from tbl where id = 1" + >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { + if(age_p == nullptr || name_p == nullptr || img_p == nullptr) { + cerr << "ERROR: values should not be null" << std::endl; + } + + cout << "age:" << *age_p << " name:" << *name_p << " img:"; + for(auto i : *img_p) cout << i << ","; cout << endl; + }; + +db << "select age,name,img from tbl where id = 2" + >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { + if(age_p != nullptr || name_p != nullptr || img_p != nullptr) { + cerr << "ERROR: values should be nullptr" << std::endl; + exit(EXIT_FAILURE); + } + + cout << "OK all three values are nullptr" << endl; + }; +``` + +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 (const 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 the optional library is not available, the experimental/optional one will be used instead. + +Variant type support (C++17) +---- +If your columns may have flexible types, you can use C++17's `std::variant` to extract the value. + +```c++ +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`. +It is also possible to use a variant with `std::monostate` in order to catch null values. + +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::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 (const sqlite_exception& e) { + cerr << e.get_code() << ": " << e.what() << " during " + << e.get_sql() << endl; +} +/* you can catch specific exceptions as well, + catch(const sqlite::errors::constraint &e) { } */ +/* and even more specific exceptions + catch(const 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 (const sqlite_exception& e) {} +``` + +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);"; + +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'; +}; +``` + +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")`. + +Installation +---- +The project is header only. +Simply point your compiler at the hdr/ directory. + +Contributing +---- +Install cmake and build the project. +Dependencies will be installed automatically (using hunter). + +```bash +mkdir build +cd ./build +cmake .. +make +``` + +Breaking Changes +---- +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) +- Nuget (TODO [nuget.org](https://www.nuget.org/)) +- Conan (TODO [conan.io](https://conan.io/)) +- [vcpkg](https://github.com/Microsoft/vcpkg) + +## License + +MIT license - [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) diff --git a/common/sqlite_modern_cpp/cmake/Catch.cmake b/common/sqlite_modern_cpp/cmake/Catch.cmake new file mode 100644 index 00000000..486e3233 --- /dev/null +++ b/common/sqlite_modern_cpp/cmake/Catch.cmake @@ -0,0 +1,175 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +Catch +----- + +This module defines a function to help use the Catch test framework. + +The :command:`catch_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each Catch test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: catch_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + catch_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [TEST_LIST var] + ) + + ``catch_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-names-only`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``_TESTS`` variable. + + The options are: + + ``target`` + Specifies the Catch executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the Catch executable with the ``--list-test-names-only`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``catch_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``catch_discover_tests``. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``catch_discover_tests()``. + Note that this variable is only available in CTest. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(catch_discover_tests TARGET) + cmake_parse_arguments( + "" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + ## Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX=${_TEST_PREFIX}" + -D "TEST_SUFFIX=${_TEST_SUFFIX}" + -D "TEST_LIST=${_TEST_LIST}" + -D "CTEST_FILE=${ctest_tests_file}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if (NOT ${test_include_file_set}) + set_property(DIRECTORY + PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + ) + else() + message(FATAL_ERROR + "Cannot set more than one TEST_INCLUDE_FILE" + ) + endif() + endif() + +endfunction() + +############################################################################### + +set(_CATCH_DISCOVER_TESTS_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake +) diff --git a/common/sqlite_modern_cpp/cmake/CatchAddTests.cmake b/common/sqlite_modern_cpp/cmake/CatchAddTests.cmake new file mode 100644 index 00000000..81d50b8b --- /dev/null +++ b/common/sqlite_modern_cpp/cmake/CatchAddTests.cmake @@ -0,0 +1,77 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${TEST_EXECUTABLE}' does not exist" + ) +endif() +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only + OUTPUT_VARIABLE output + RESULT_VARIABLE result +) +# Catch --list-test-names-only reports the number of tests, so 0 is... surprising +if(${result} EQUAL 0) + message(WARNING + "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" + ) +elseif(${result} LESS 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") + +# Parse output +foreach(line ${output}) + # Test name; strip spaces to get just the name... + string(REGEX REPLACE "^ +" "" test "${line}") + # ...and add to script + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "${test}" + ${extra_args} + ) + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ${properties} + ) + list(APPEND tests "${prefix}${test}${suffix}") +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set +# properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/common/sqlite_modern_cpp/cmake/HunterGate.cmake b/common/sqlite_modern_cpp/cmake/HunterGate.cmake new file mode 100644 index 00000000..45438f11 --- /dev/null +++ b/common/sqlite_modern_cpp/cmake/HunterGate.cmake @@ -0,0 +1,542 @@ +# Copyright (c) 2013-2017, Ruslan Baratov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This is a gate file to Hunter package manager. +# Include this file using `include` command and add package you need, example: +# +# cmake_minimum_required(VERSION 3.0) +# +# include("cmake/HunterGate.cmake") +# HunterGate( +# URL "https://github.com/path/to/hunter/archive.tar.gz" +# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" +# ) +# +# project(MyProject) +# +# hunter_add_package(Foo) +# hunter_add_package(Boo COMPONENTS Bar Baz) +# +# Projects: +# * https://github.com/hunter-packages/gate/ +# * https://github.com/ruslo/hunter + +option(HUNTER_ENABLED "Enable Hunter package manager support" ON) +if(HUNTER_ENABLED) + if(CMAKE_VERSION VERSION_LESS "3.0") + message(FATAL_ERROR "At least CMake version 3.0 required for hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF.") + endif() +endif() + +include(CMakeParseArguments) # cmake_parse_arguments + +option(HUNTER_STATUS_PRINT "Print working status" ON) +option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) + +set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki") + +function(hunter_gate_status_print) + foreach(print_message ${ARGV}) + if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) + message(STATUS "[hunter] ${print_message}") + endif() + endforeach() +endfunction() + +function(hunter_gate_status_debug) + foreach(print_message ${ARGV}) + if(HUNTER_STATUS_DEBUG) + string(TIMESTAMP timestamp) + message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") + endif() + endforeach() +endfunction() + +function(hunter_gate_wiki wiki_page) + message("------------------------------ WIKI -------------------------------") + message(" ${HUNTER_WIKI}/${wiki_page}") + message("-------------------------------------------------------------------") + message("") + message(FATAL_ERROR "") +endfunction() + +function(hunter_gate_internal_error) + message("") + foreach(print_message ${ARGV}) + message("[hunter ** INTERNAL **] ${print_message}") + endforeach() + message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("error.internal") +endfunction() + +function(hunter_gate_fatal_error) + cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}") + string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki) + if(have_no_wiki) + hunter_gate_internal_error("Expected wiki") + endif() + message("") + foreach(x ${hunter_UNPARSED_ARGUMENTS}) + message("[hunter ** FATAL ERROR **] ${x}") + endforeach() + message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("${hunter_WIKI}") +endfunction() + +function(hunter_gate_user_error) + hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data") +endfunction() + +function(hunter_gate_self root version sha1 result) + string(COMPARE EQUAL "${root}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("root is empty") + endif() + + string(COMPARE EQUAL "${version}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("version is empty") + endif() + + string(COMPARE EQUAL "${sha1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("sha1 is empty") + endif() + + string(SUBSTRING "${sha1}" 0 7 archive_id) + + if(EXISTS "${root}/cmake/Hunter") + set(hunter_self "${root}") + else() + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + endif() + + set("${result}" "${hunter_self}" PARENT_SCOPE) +endfunction() + +# Set HUNTER_GATE_ROOT cmake variable to suitable value. +function(hunter_gate_detect_root) + # Check CMake variable + string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") + return() + endif() + + # Check environment variable + string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") + return() + endif() + + # Check HOME environment variable + string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") + return() + endif() + + # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) + if(WIN32) + string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + ) + return() + endif() + + string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using USERPROFILE environment variable" + ) + return() + endif() + endif() + + hunter_gate_fatal_error( + "Can't detect HUNTER_ROOT" + WIKI "error.detect.hunter.root" + ) +endfunction() + +macro(hunter_gate_lock dir) + if(NOT HUNTER_SKIP_LOCK) + if("${CMAKE_VERSION}" VERSION_LESS "3.2") + hunter_gate_fatal_error( + "Can't lock, upgrade to CMake 3.2 or use HUNTER_SKIP_LOCK" + WIKI "error.can.not.lock" + ) + endif() + hunter_gate_status_debug("Locking directory: ${dir}") + file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) + hunter_gate_status_debug("Lock done") + endif() +endmacro() + +function(hunter_gate_download dir) + string( + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall + ) + if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) + hunter_gate_fatal_error( + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + WIKI "error.run.install" + ) + endif() + string(COMPARE EQUAL "${dir}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("Empty 'dir' argument") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_URL empty") + endif() + + set(done_location "${dir}/DONE") + set(sha1_location "${dir}/SHA1") + + set(build_dir "${dir}/Build") + set(cmakelists "${dir}/CMakeLists.txt") + + hunter_gate_lock("${dir}") + if(EXISTS "${done_location}") + # while waiting for lock other instance can do all the job + hunter_gate_status_debug("File '${done_location}' found, skip install") + return() + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(MAKE_DIRECTORY "${build_dir}") # check directory permissions + + # Disabling languages speeds up a little bit, reduces noise in the output + # and avoids path too long windows error + file( + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.0)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ON\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" + ) + + if(HUNTER_STATUS_DEBUG) + set(logging_params "") + else() + set(logging_params OUTPUT_QUIET) + endif() + + hunter_gate_status_debug("Run generate") + + # Need to add toolchain file too. + # Otherwise on Visual Studio + MDD this will fail with error: + # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" + if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") + get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") + else() + # 'toolchain_arg' can't be empty + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") + endif() + + string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) + if(no_make) + set(make_arg "") + else() + # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM + set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Configure project failed") + endif() + + hunter_gate_status_print( + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" + ) + execute_process( + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Build project failed") + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") + file(WRITE "${done_location}" "DONE") + + hunter_gate_status_debug("Finished") +endfunction() + +# Must be a macro so master file 'cmake/Hunter' can +# apply all variables easily just by 'include' command +# (otherwise PARENT_SCOPE magic needed) +macro(HunterGate) + if(HUNTER_GATE_DONE) + # variable HUNTER_GATE_DONE set explicitly for external project + # (see `hunter_download`) + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() + + # First HunterGate command will init Hunter, others will be ignored + get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) + + if(NOT HUNTER_ENABLED) + # Empty function to avoid error "unknown function" + function(hunter_add_package) + endfunction() + + set( + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + ) + if(EXISTS "${_hunter_gate_disabled_mode_dir}") + hunter_gate_status_debug( + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + ) + list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") + endif() + elseif(_hunter_gate_done) + hunter_gate_status_debug("Secondary HunterGate (use old settings)") + hunter_gate_self( + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self + ) + include("${_hunter_self}/cmake/Hunter") + else() + set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_LIST_DIR}") + + string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) + if(_have_project_name) + hunter_gate_fatal_error( + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + WIKI "error.huntergate.before.project" + ) + endif() + + cmake_parse_arguments( + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + ) + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) + string( + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed + ) + string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) + string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) + + if(_have_unparsed) + hunter_gate_user_error( + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + ) + endif() + if(_empty_sha1) + hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") + endif() + if(_empty_url) + hunter_gate_user_error("URL suboption of HunterGate is mandatory") + endif() + if(_have_global) + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") + endif() + endif() + if(HUNTER_GATE_LOCAL) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") + endif() + endif() + if(_have_filepath) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") + endif() + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") + endif() + endif() + + hunter_gate_detect_root() # set HUNTER_GATE_ROOT + + # Beautify path, fix probable problems with windows path slashes + get_filename_component( + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + ) + hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") + if(NOT HUNTER_ALLOW_SPACES_IN_PATH) + string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) + if(NOT _contain_spaces EQUAL -1) + hunter_gate_fatal_error( + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + WIKI "error.spaces.in.hunter.root" + ) + endif() + endif() + + string( + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" + ) + string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) + if(_is_empty) + set(HUNTER_GATE_VERSION "unknown") + endif() + + hunter_gate_self( + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self + ) + + set(_master_location "${_hunter_self}/cmake/Hunter") + if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") + # Hunter downloaded manually (e.g. by 'git clone') + set(_unused "xxxxxxxxxx") + set(HUNTER_GATE_SHA1 "${_unused}") + set(HUNTER_GATE_VERSION "${_unused}") + else() + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") + + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() + + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() + + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() + endif() + include("${_master_location}") + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() +endmacro() diff --git a/common/sqlite_modern_cpp/cmake/ParseAndAddCatchTests.cmake b/common/sqlite_modern_cpp/cmake/ParseAndAddCatchTests.cmake new file mode 100644 index 00000000..cb2846d0 --- /dev/null +++ b/common/sqlite_modern_cpp/cmake/ParseAndAddCatchTests.cmake @@ -0,0 +1,185 @@ +#==================================================================================================# +# supported macros # +# - TEST_CASE, # +# - SCENARIO, # +# - TEST_CASE_METHOD, # +# - CATCH_TEST_CASE, # +# - CATCH_SCENARIO, # +# - CATCH_TEST_CASE_METHOD. # +# # +# Usage # +# 1. make sure this module is in the path or add this otherwise: # +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # +# 2. make sure that you've enabled testing option for the project by the call: # +# enable_testing() # +# 3. add the lines to the script for testing target (sample CMakeLists.txt): # +# project(testing_target) # +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # +# enable_testing() # +# # +# find_path(CATCH_INCLUDE_DIR "catch.hpp") # +# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) # +# # +# file(GLOB SOURCE_FILES "*.cpp") # +# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) # +# # +# include(ParseAndAddCatchTests) # +# ParseAndAddCatchTests(${PROJECT_NAME}) # +# # +# The following variables affect the behavior of the script: # +# # +# PARSE_CATCH_TESTS_VERBOSE (Default OFF) # +# -- enables debug messages # +# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) # +# -- excludes tests marked with [!hide], [.] or [.foo] tags # +# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) # +# -- adds fixture class name to the test name # +# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) # +# -- adds cmake target name to the test name # +# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # +# -- causes CMake to rerun when file with tests changes so that new tests will be discovered # +# # +#==================================================================================================# + +cmake_minimum_required(VERSION 2.8.8) + +option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) +option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) +option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON) +option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) +option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) + +function(PrintDebugMessage) + if(PARSE_CATCH_TESTS_VERBOSE) + message(STATUS "ParseAndAddCatchTests: ${ARGV}") + endif() +endfunction() + +# This removes the contents between +# - block comments (i.e. /* ... */) +# - full line comments (i.e. // ... ) +# contents have been read into '${CppCode}'. +# !keep partial line comments +function(RemoveComments CppCode) + string(ASCII 2 CMakeBeginBlockComment) + string(ASCII 3 CMakeEndBlockComment) + string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}") + + set(${CppCode} "${${CppCode}}" PARENT_SCOPE) +endfunction() + +# Worker function +function(ParseFile SourceFile TestTarget) + # According to CMake docs EXISTS behavior is well-defined only for full paths. + get_filename_component(SourceFile ${SourceFile} ABSOLUTE) + if(NOT EXISTS ${SourceFile}) + message(WARNING "Cannot find source file: ${SourceFile}") + return() + endif() + PrintDebugMessage("parsing ${SourceFile}") + file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) + + # Remove block and fullline comments + RemoveComments(Contents) + + # Find definition of test names + string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") + + if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) + PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") + set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} + ) + endif() + + foreach(TestName ${Tests}) + # Strip newlines + string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") + + # Get test type and fixture if applicable + string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") + string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") + string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}") + + # Get string parts of test definition + string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") + + # Strip wrapping quotation marks + string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") + string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") + + # Validate that a test name and tags have been provided + list(LENGTH TestStrings TestStringsLength) + if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) + message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") + endif() + + # Assign name and tags + list(GET TestStrings 0 Name) + if("${TestType}" STREQUAL "SCENARIO") + set(Name "Scenario: ${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture) + set(CTestName "${TestFixture}:${Name}") + else() + set(CTestName "${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) + set(CTestName "${TestTarget}:${CTestName}") + endif() + # add target to labels to enable running all tests added from this target + set(Labels ${TestTarget}) + if(TestStringsLength EQUAL 2) + list(GET TestStrings 1 Tags) + string(TOLOWER "${Tags}" Tags) + # remove target from labels if the test is hidden + if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") + list(REMOVE_ITEM Labels ${TestTarget}) + endif() + string(REPLACE "]" ";" Tags "${Tags}") + string(REPLACE "[" "" Tags "${Tags}") + endif() + + list(APPEND Labels ${Tags}) + + list(FIND Labels "!hide" IndexOfHideLabel) + set(HiddenTagFound OFF) + foreach(label ${Labels}) + string(REGEX MATCH "^!hide|^\\." result ${label}) + if(result) + set(HiddenTagFound ON) + break() + endif(result) + endforeach(label) + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound}) + PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") + else() + PrintDebugMessage("Adding test \"${CTestName}\"") + if(Labels) + PrintDebugMessage("Setting labels to ${Labels}") + endif() + + # Add the test and set its properties + add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters}) + set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" + LABELS "${Labels}") + endif() + + endforeach() +endfunction() + +# entry point +function(ParseAndAddCatchTests TestTarget) + PrintDebugMessage("Started parsing ${TestTarget}") + get_target_property(SourceFiles ${TestTarget} SOURCES) + PrintDebugMessage("Found the following sources: ${SourceFiles}") + foreach(SourceFile ${SourceFiles}) + ParseFile(${SourceFile} ${TestTarget}) + endforeach() + PrintDebugMessage("Finished parsing ${TestTarget}") +endfunction() diff --git a/common/sqlite_modern_cpp.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp.h similarity index 100% rename from common/sqlite_modern_cpp.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp.h diff --git a/common/sqlite_modern_cpp/errors.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/errors.h similarity index 100% rename from common/sqlite_modern_cpp/errors.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/errors.h diff --git a/common/sqlite_modern_cpp/lists/error_codes.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/lists/error_codes.h similarity index 100% rename from common/sqlite_modern_cpp/lists/error_codes.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/lists/error_codes.h diff --git a/common/sqlite_modern_cpp/log.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/log.h similarity index 100% rename from common/sqlite_modern_cpp/log.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/log.h diff --git a/common/sqlite_modern_cpp/sqlcipher.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/sqlcipher.h similarity index 100% rename from common/sqlite_modern_cpp/sqlcipher.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/sqlcipher.h diff --git a/common/sqlite_modern_cpp/type_wrapper.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/type_wrapper.h similarity index 100% rename from common/sqlite_modern_cpp/type_wrapper.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/type_wrapper.h diff --git a/common/sqlite_modern_cpp/utility/function_traits.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/function_traits.h similarity index 100% rename from common/sqlite_modern_cpp/utility/function_traits.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/function_traits.h diff --git a/common/sqlite_modern_cpp/utility/uncaught_exceptions.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/uncaught_exceptions.h similarity index 100% rename from common/sqlite_modern_cpp/utility/uncaught_exceptions.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/uncaught_exceptions.h diff --git a/common/sqlite_modern_cpp/utility/utf16_utf8.h b/common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/utf16_utf8.h similarity index 100% rename from common/sqlite_modern_cpp/utility/utf16_utf8.h rename to common/sqlite_modern_cpp/hdr/sqlite_modern_cpp/utility/utf16_utf8.h diff --git a/common/sqlite_modern_cpp/tests/blob_example.cc b/common/sqlite_modern_cpp/tests/blob_example.cc new file mode 100644 index 00000000..f019eb55 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/blob_example.cc @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +TEST_CASE("Blob does work", "[blob]") { + database db(":memory:"); + + db << "CREATE TABLE person (name TEXT, numbers BLOB);"; + db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; + db << "INSERT INTO person VALUES (?, ?)" << "jack" << 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; + + REQUIRE(numbers_bob.size() == 4); + REQUIRE((numbers_bob[0] == 1 && numbers_bob[1] == 2 && numbers_bob[2] == 3 && numbers_bob[3] == 4)); + + vector numbers_jack; + db << "SELECT numbers from person where name = ?;" << "jack" >> numbers_jack; + + REQUIRE(numbers_jack.size() == 4); + REQUIRE((numbers_jack[0] == '1' && numbers_jack[1] == '2' && numbers_jack[2] == '3' && numbers_jack[3] == '4')); + + + vector numbers_sara; + db << "SELECT numbers from person where name = ?;" << "sara" >> numbers_sara; + + REQUIRE(numbers_sara.size() == 4); + REQUIRE((numbers_sara[0] == 1.0 && numbers_sara[1] == 2.0 && numbers_sara[2] == 3.0 && numbers_sara[3] == 4.0)); +} diff --git a/common/sqlite_modern_cpp/tests/error_log.cc b/common/sqlite_modern_cpp/tests/error_log.cc new file mode 100644 index 00000000..3c5606bd --- /dev/null +++ b/common/sqlite_modern_cpp/tests/error_log.cc @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct TrackErrors { + TrackErrors() + : constraint_called{false}, primarykey_called{false} + { + error_log( + [this](errors::constraint) { + constraint_called = true; + }, + [this](errors::constraint_primarykey e) { + primarykey_called = true; + } + // We are not registering the unique key constraint: + // For a unique key error the first handler (errors::constraint) will be called instead. + ); + } + + bool constraint_called; + bool primarykey_called; + /* bool unique_called; */ +}; + +// Run before main, before any other sqlite function. +static TrackErrors track; + + +TEST_CASE("error_log works", "[log]") { + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT unique);"; + + SECTION("An extended error code gets called when registered") { + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // triger primarykey constraint of 'id' + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "bob"; + } catch (const errors::constraint& e) { } + REQUIRE(track.primarykey_called == true); + REQUIRE(track.constraint_called == false); + track.primarykey_called = false; + } + + SECTION("Parent gets called when the exact error code is not registered") { + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // trigger unique constraint of 'name' + db << "INSERT INTO person (id,name) VALUES (?,?)" << 2 << "jack"; + } catch (const errors::constraint& e) { } + + REQUIRE(track.primarykey_called == false); + REQUIRE(track.constraint_called == true); + track.constraint_called = false; + } +} diff --git a/common/sqlite_modern_cpp/tests/exception_dont_execute.cc b/common/sqlite_modern_cpp/tests/exception_dont_execute.cc new file mode 100644 index 00000000..156ac315 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/exception_dont_execute.cc @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +TEST_CASE("Prepared statement will not execute on exceptions", "[prepared_statements]") { + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);"; + + try { + auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)"; + throw 1; + } catch (int) { } + + int count; + db << "select count(*) from person" >> count; + REQUIRE(count == 0); +} diff --git a/common/sqlite_modern_cpp/tests/exception_dont_execute_nested.cc b/common/sqlite_modern_cpp/tests/exception_dont_execute_nested.cc new file mode 100644 index 00000000..a416829d --- /dev/null +++ b/common/sqlite_modern_cpp/tests/exception_dont_execute_nested.cc @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct A { + ~A() { + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);"; + + try { + auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)"; + throw 1; + } catch (int) { + } + } +}; + +TEST_CASE("Nested prepered statements wont execute", "[nested_prepared_statements]") { +#ifdef MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT + try { + A a; + throw 1; + } catch(int) { } +#else +#endif +} diff --git a/common/sqlite_modern_cpp/tests/exceptions.cc b/common/sqlite_modern_cpp/tests/exceptions.cc new file mode 100644 index 00000000..5905e047 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/exceptions.cc @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +TEST_CASE("exceptions are thrown", "[exceptions]") { + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + bool expception_thrown = false; + std::string get_sql_result; + +#if SQLITE_VERSION_NUMBER >= 3014000 + std::string expedted_sql = "INSERT INTO person (id,name) VALUES (1,'jack')"; +#else + std::string expedted_sql = "INSERT INTO person (id,name) VALUES (?,?)"; +#endif + + SECTION("Parent exception works") { + 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) { + expception_thrown = true; + get_sql_result = e.get_sql(); + } + + REQUIRE(expception_thrown == true); + REQUIRE(get_sql_result == expedted_sql); + } + + SECTION("Extended exception works") { + 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) { + expception_thrown = true; + get_sql_result = e.get_sql(); + } + + REQUIRE(expception_thrown == true); + REQUIRE(get_sql_result == expedted_sql); + } +} diff --git a/common/sqlite_modern_cpp/tests/flags.cc b/common/sqlite_modern_cpp/tests/flags.cc new file mode 100644 index 00000000..e1af2b70 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/flags.cc @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct TmpFile { + string fname; + + TmpFile(): fname("./flags.db") { } + ~TmpFile() { remove(fname.c_str()); } +}; +#ifdef _WIN32 +#define OUR_UTF16 "UTF-16le" +#elif BYTE_ORDER == BIG_ENDIAN +#define OUR_UTF16 "UTF-16be" +#else +#define OUR_UTF16 "UTF-16le" +#endif + +TEST_CASE("flags work", "[flags]") { + TmpFile file; + sqlite::sqlite_config cfg; + std::string enc; + SECTION("PRAGMA endcoding is UTF-8 for string literals") { + database db(":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + REQUIRE(enc == "UTF-8"); + } + SECTION("encoding is UTF-16 for u"" prefixed string literals") { + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + REQUIRE(enc == OUR_UTF16); + } + SECTION("we can set encoding to UTF-8 with flags") { + cfg.encoding = Encoding::UTF8; + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + REQUIRE(enc == "UTF-8"); + } + SECTION("we can set encoding to UTF-16 with flags") { + cfg.encoding = Encoding::UTF16; + database db(u":memory:", cfg); + db << "PRAGMA encoding;" >> enc; + REQUIRE(enc == OUR_UTF16); + } + SECTION("we can set encoding to UTF-16 with flags for on disk databases") { + cfg.encoding = Encoding::UTF16; + database db(file.fname, cfg); + db << "PRAGMA encoding;" >> enc; + REQUIRE(enc == OUR_UTF16); + + } + SECTION("READONLY flag works") { + { + database db(file.fname, cfg); + 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; + + REQUIRE(str == "hello"); + + bool failed = false; + try { + db << "INSERT INTO foo VALUES (?)" << "invalid"; + } catch(errors::readonly&) { + failed = true; + } + REQUIRE(failed == true); + } +} diff --git a/common/sqlite_modern_cpp/tests/functions.cc b/common/sqlite_modern_cpp/tests/functions.cc new file mode 100644 index 00000000..b7a7badb --- /dev/null +++ b/common/sqlite_modern_cpp/tests/functions.cc @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +int add_integers(int i, int j) { + return i+j; +} +TEST_CASE("sql functions work", "[functions]") { + database db(":memory:"); + + 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", &add_integers); + + std::string test1, test3; + int test2 = 0; + db << "select my_new_concat('Hello ','world!')" >> test1; + db << "select add_integers(1,1)" >> test2; + db << "select my_new_concat('a','b','c')" >> test3; + + REQUIRE(test1 == "Hello world!"); + REQUIRE(test2 == 2); + REQUIRE(test3 == "abc"); + + db.define("my_count", [](int &i, int) {++i;}, [](int &i) {return i;}); + db.define("my_concat_aggregate", [](std::string &stored, std::string current) {stored += current;}, [](std::string &stored) {return stored;}); + + db << "create table countable(i, s)"; + db << "insert into countable values(1, 'a')"; + db << "insert into countable values(2, 'b')"; + db << "insert into countable values(3, 'c')"; + db << "select my_count(i) from countable" >> test2; + db << "select my_concat_aggregate(s) from countable order by i" >> test3; + + REQUIRE(test2 == 3); + REQUIRE(test3 == "abc"); + + 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; + + db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) { + /* cout << number << "! = " << factorial << '\n'; */ + }; +} diff --git a/common/sqlite_modern_cpp/tests/functors.cc b/common/sqlite_modern_cpp/tests/functors.cc new file mode 100644 index 00000000..3c8b5024 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/functors.cc @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include + +using namespace sqlite; +using namespace std; + +struct tbl_functor { + explicit tbl_functor(vector > &vec_) : vec(vec_) { } + + void operator() ( int id, string name) { + vec.push_back(make_pair(id, move(name))); + } + vector > &vec; +}; + +TEST_CASE("functors work", "[functors]") { + database db(":memory:"); + db << "CREATE TABLE tbl (id integer, name string);"; + db << "INSERT INTO tbl VALUES (?, ?);" << 1 << "hello"; + db << "INSERT INTO tbl VALUES (?, ?);" << 2 << "world"; + + vector > vec; + db << "select id,name from tbl;" >> tbl_functor(vec); + + REQUIRE(vec.size() == 2); + + vec.clear(); + + tbl_functor functor(vec); + db << "select id,name from tbl;" >> functor; + + REQUIRE(vec.size() == 2); + REQUIRE(vec[0].first == 1); + REQUIRE(vec[0].second == "hello"); +} diff --git a/common/sqlite_modern_cpp/tests/lvalue_functor.cc b/common/sqlite_modern_cpp/tests/lvalue_functor.cc new file mode 100644 index 00000000..ba48995d --- /dev/null +++ b/common/sqlite_modern_cpp/tests/lvalue_functor.cc @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +template +struct builder { + vector results; + + void operator()(AttrTypes... args) { + results.emplace_back(std::forward(args)...); + }; +}; + + +struct user { + int age; + string name; + double weight; + + user(int age, string name, double weight) : age(age), name(name), weight(weight) { } + + static std::vector all(sqlite::database& db) { + builder person_builder; + db << "SELECT * FROM user;" + >> person_builder; + return std::move(person_builder.results); // move to avoid copying data ;-) + }; +}; + +TEST_CASE("lvalue functors work", "[lvalue_functor]") { + + database db(":memory:"); + + db << + "create table if not exists user (" + " age int," + " name text," + " weight real" + ");"; + + db << "insert into user (age,name,weight) values (?,?,?);" << 20 << u"chandler" << 83.25; + db << "insert into user (age,name,weight) values (?,?,?);" << 21 << u"monika" << 86.25; + db << "insert into user (age,name,weight) values (?,?,?);" << 22 << u"ross" << 88.25; + + auto users = user::all(db); + + REQUIRE(users.size() == 3); +} diff --git a/common/sqlite_modern_cpp/tests/mov_ctor.cc b/common/sqlite_modern_cpp/tests/mov_ctor.cc new file mode 100644 index 00000000..95389b20 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/mov_ctor.cc @@ -0,0 +1,31 @@ +// Fixing https://github.com/SqliteModernCpp/sqlite_modern_cpp/issues/63 +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct dbFront { + std::unique_ptr storedProcedure; + database db; + dbFront(): db(":memory:") { + db << "CREATE TABLE tbl (id integer, name string);"; + // the temporary moved object should not run _execute() function on destruction. + storedProcedure = std::make_unique( + db << "INSERT INTO tbl VALUES (?, ?);" + ); + } +}; + + +TEST_CASE("database lifecycle", "move_ctor") { + + bool failed = false; + try { dbFront dbf; } + catch(const sqlite_exception& e) { failed = true; } + catch(...) { failed = true; } + + REQUIRE(failed == false); +} diff --git a/common/sqlite_modern_cpp/tests/named.cc b/common/sqlite_modern_cpp/tests/named.cc new file mode 100644 index 00000000..2b255c4d --- /dev/null +++ b/common/sqlite_modern_cpp/tests/named.cc @@ -0,0 +1,20 @@ +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +TEST_CASE("binding named parameters works", "[named]") { + database db(":memory:"); + + db << "CREATE TABLE foo (a,b);"; + + int a = 1; + db << "INSERT INTO foo VALUES (:first,:second)" << named_parameter(":second", 2) << named_parameter(":first", a); + + db << "SELECT b FROM foo WHERE a=?;" << 1 >> a; + + REQUIRE(a == 2); +} \ No newline at end of file diff --git a/common/sqlite_modern_cpp/tests/nullptr_uniqueptr.cc b/common/sqlite_modern_cpp/tests/nullptr_uniqueptr.cc new file mode 100644 index 00000000..f4851920 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/nullptr_uniqueptr.cc @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +using namespace std; +using namespace sqlite; + +TEST_CASE("nullptr & unique_ptr", "[null_ptr_unique_ptr]") { + database db(":memory:"); + db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);"; + db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector { 1, 2 , 3}; + unique_ptr ptr_null; + db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr; + + db << "select age,name,img from tbl where id = 1" >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { + REQUIRE(age_p != nullptr); + REQUIRE(name_p != nullptr); + REQUIRE(img_p != nullptr); + }; + + db << "select age,name,img from tbl where id = 2" >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { + REQUIRE(age_p == nullptr); + REQUIRE(name_p == nullptr); + REQUIRE(img_p == nullptr); + }; +} diff --git a/common/sqlite_modern_cpp/tests/prepared_statment.cc b/common/sqlite_modern_cpp/tests/prepared_statment.cc new file mode 100644 index 00000000..b5aa3028 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/prepared_statment.cc @@ -0,0 +1,102 @@ +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +TEST_CASE("prepared statements work", "[prepared_statement]") { + database db(":memory:"); + + auto pps = db << "select ?"; // get a prepared parsed and ready statment + + int test = 4; + pps << test; // set a bound var + + pps >> test; // execute statement + + REQUIRE(test == 4); + + pps << 4; // bind a rvalue + pps++; // and execute + + pps << 8 >> test; + + REQUIRE(test == 8); + + auto pps2 = db << "select 1,2"; // multiple extract test + + pps2 >> [](int a, int b) { + REQUIRE(a == 1); + REQUIRE(b == 2); + }; + + auto pps3 = db << "select ?,?,?"; + + test = 2; + pps3 << 1 << test << 3 >> [](int a, int b, int c) { + REQUIRE(a == 1); + REQUIRE(b == 2); + REQUIRE(c == 3); + }; + + test = 1; + db << "select ?,?" << test << 5 >> test; // and mow everything together + REQUIRE(test == 1); + + test = 2; + db << "select ?,?,?" << 1 << test << 3 >> [](int a, int b, int c) { + REQUIRE(a == 1); + REQUIRE(b == 2); + REQUIRE(c == 3); + }; + + db << "select ?" << test; // noVal + db << "select ?,?" << test << 1; + db << "select ?,?" << 1 << test; + db << "select ?,?" << 1 << 1; + db << "select ?,?" << test << test; + + db << "select ?" << test >> test; // lVal + db << "select ?,?" << test << 1 >> test; + db << "select ?,?" << 1 << test >> test; + db << "select ?,?" << 1 << 1 >> test; + db << "select ?,?" << test << test >> test; + + int q = 0; + test = 1; + db << "select ?" << test >> [&](int t) { q = t; }; // rVal + REQUIRE(q == 1); + + db << "select ?,?" << test << 1 >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << 1 << test >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << 1 << 1 >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << test << test >> [&](int t, int p) { q = t + p; }; + + db << "select ?,?,?" << test << 1 << test; // mix + db << "select ?,?,?" << 1 << test << 1; + db << "select ?,?,?" << 1 << 1 << test; + db << "select ?,?,?" << 1 << 1 << 1; + db << "select ?,?,?" << test << test << test; + + { + auto pps4 = db << "select ?,?,?"; // reuse + + (pps4 << test << 1 << test)++; + (pps4 << 1 << test << 1)++; + (pps4 << 1 << 1 << test)++; + (pps4 << 1 << 1 << 1)++; + (pps4 << test << test << test)++; + } + + { + auto prep = db << "select ?"; + + prep << 5; + prep.execute(); + prep << 6; + prep.execute(); + } + + +} diff --git a/common/sqlite_modern_cpp/tests/readme_example.cc b/common/sqlite_modern_cpp/tests/readme_example.cc new file mode 100644 index 00000000..d5137fc8 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/readme_example.cc @@ -0,0 +1,64 @@ +#define CATCH_CONFIG_MAIN +#include +#include +#include + +using namespace sqlite; +using namespace std; + +TEST_CASE("README Example Works", "[readme]") { + + database db(":memory:"); + + db << + "create table if not exists user (" + " _id integer primary key autoincrement not null," + " age int," + " name text," + " weight real" + ");"; + + db << "insert into user (age,name,weight) values (?,?,?);" + << 20 + << u"bob" + << 83.25; + + int age = 22; float weight = 68.5; string name = "jack"; + db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string + << age + << name + << weight; + + REQUIRE(db.last_insert_rowid() != 0); + + db << "select age,name,weight from user where age > ? ;" + << 21 + >> [&](int _age, string _name, double _weight) { + REQUIRE((_age == age && _name == name)); + }; + + for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 21) { + int _age; + string _name; + double _weight; + row >> _age >> _name >> _weight; + REQUIRE((_age == age && _name == name)); + } + + for(std::tuple row : db << "select age,name,weight from user where age > ? ;" << 21) { + REQUIRE((std::get(row) == age && std::get(row) == name)); + } + + // 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; + REQUIRE(count == 2); + + db << "select age, name from user where _id=1;" >> tie(age, name); + + // this also works and the returned value will be automatically converted to string + string str_count; + db << "select count(*) from user" >> str_count; + REQUIRE(str_count == string{"2"}); +} diff --git a/common/sqlite_modern_cpp/tests/shared_connection.cc b/common/sqlite_modern_cpp/tests/shared_connection.cc new file mode 100644 index 00000000..4f270933 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/shared_connection.cc @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +using namespace sqlite; +using namespace std; + +TEST_CASE("shared connections work fine", "[shared_connection]") { + database db(":memory:"); + + { + auto con = db.connection(); + + { + database db2(con); + int test = 0; + db2 << "select 1" >> test; + REQUIRE(test == 1); + } + + int test = 0; + db << "select 1" >> test; + REQUIRE(test == 1); + } +} diff --git a/common/sqlite_modern_cpp/tests/simple_examples.cc b/common/sqlite_modern_cpp/tests/simple_examples.cc new file mode 100644 index 00000000..d8e684d3 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/simple_examples.cc @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +TEST_CASE("simple examples", "[examples]") { + database db(":memory:"); + + 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=?;" << 2L >> str; + + REQUIRE(str == "world"); + + std::string sql("select 1+1"); + long test = 0; + db << sql >> test; + + REQUIRE(test == 2); + + db << "UPDATE foo SET b=? WHERE a=?;" << "hi" << 1L; + db << "SELECT b FROM foo WHERE a=?;" << 1L >> str; + + REQUIRE(str == "hi"); + REQUIRE(db.rows_modified() == 1); + + db << "UPDATE foo SET b=?;" << "hello world"; + + REQUIRE(db.rows_modified() == 2); +} diff --git a/common/sqlite_modern_cpp/tests/sqlcipher.cc b/common/sqlite_modern_cpp/tests/sqlcipher.cc new file mode 100644 index 00000000..2fc2f10c --- /dev/null +++ b/common/sqlite_modern_cpp/tests/sqlcipher.cc @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include +using namespace sqlite; +using namespace std; + +struct TmpFile +{ + string fname; + + TmpFile(): fname("./sqlcipher.db") { } + ~TmpFile() { remove(fname.c_str()); } +}; + +TEST_CASE("sqlcipher works", "[sqlcipher]") { + TmpFile file; + sqlcipher_config config; + { + config.key = "DebugKey"; + sqlcipher_database db(file.fname, config); + + db << "CREATE TABLE foo (a integer, b string);"; + 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; + + REQUIRE(str == "world"); + } + + bool failed = false; + try { + config.key = "DebugKey2"; + sqlcipher_database db(file.fname, config); + db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; + } catch(const errors::notadb&) { + failed = true; + // Expected, wrong key + } + REQUIRE(failed == true); + + { + config.key = "DebugKey"; + sqlcipher_database db(file.fname, config); + db.rekey("DebugKey2"); + } + { + config.key = "DebugKey2"; + sqlcipher_database db(file.fname, config); + db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; + } +} diff --git a/common/sqlite_modern_cpp/tests/std_optional.cc b/common/sqlite_modern_cpp/tests/std_optional.cc new file mode 100644 index 00000000..c4e6c552 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/std_optional.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace sqlite; +using namespace std; + +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT +TEST_CASE("std::optional works", "[optional]") { + database db(":memory:"); + + db << "drop table if exists test"; + db << + "create table if not exists test (" + " id integer primary key," + " val int" + ");"; + + db << "insert into test(id,val) values(?,?)" << 1 << 5; + db << "select id,val from test" >> [&](long long, sqlite::optional val) { + REQUIRE(val); + }; + + db << "delete from test where id = 1"; + db << "insert into test(id,val) values(?,?)" << 1 << nullptr; + db << "select id,val from test" >> [&](long long, sqlite::optional val) { + REQUIRE(!val); + }; + +} +#endif diff --git a/common/sqlite_modern_cpp/tests/string_view.cc b/common/sqlite_modern_cpp/tests/string_view.cc new file mode 100644 index 00000000..39b13d80 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/string_view.cc @@ -0,0 +1,23 @@ +#include +#include + + +#ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT +#include + +using namespace sqlite; +TEST_CASE("std::string_view works", "[string_view]") { + database db(":memory:"); + db << "CREATE TABLE foo (a integer, b string);\n"; + const std::string_view test1 = "null terminated string view"; + db << "INSERT INTO foo VALUES (?, ?)" << 1 << test1; + std::string str; + db << "SELECT b from FOO where a=?;" << 1 >> str; + REQUIRE(test1 == str); + const char s[] = "hello world"; + std::string_view test2(&s[0], 2); + db << "INSERT INTO foo VALUES (?,?)" << 2 << test2; + db << "SELECT b from FOO where a=?" << 2 >> str; + REQUIRE(str == "he"); +} +#endif diff --git a/common/sqlite_modern_cpp/tests/trycatchblocks.cc b/common/sqlite_modern_cpp/tests/trycatchblocks.cc new file mode 100644 index 00000000..2c0f7620 --- /dev/null +++ b/common/sqlite_modern_cpp/tests/trycatchblocks.cc @@ -0,0 +1,80 @@ +#include +#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; + } +}; + +TEST_CASE("try catch blocks", "[trycatchblocks]") { + // -------------------------------------------------------------------------- + // -- 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" ); + REQUIRE(interf.TestData() == true); + } +} diff --git a/common/sqlite_modern_cpp/tests/variant.cc b/common/sqlite_modern_cpp/tests/variant.cc new file mode 100644 index 00000000..ae755dac --- /dev/null +++ b/common/sqlite_modern_cpp/tests/variant.cc @@ -0,0 +1,57 @@ +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT +TEST_CASE("std::variant works", "[variant]") { + 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; + + REQUIRE(v.index() == 1); + REQUIRE(std::get<1>(v) == 1); + + db << "SELECT NULL" >> v; + REQUIRE(!std::get<2>(v)); + + db << "SELECT 0.0" >> v; + REQUIRE(std::get<2>(v)); +} + +TEST_CASE("std::monostate is a nullptr substitute", "[monostate]") { + database db(":memory:"); + db << "CREATE TABLE foo (a);"; + + std::variant v; + v=std::monostate(); + db << "INSERT INTO foo VALUES (?)" << v; + db << "INSERT INTO foo VALUES (?)" << "This isn't a monostate!"; + + bool found_null = false, + found_string = false; + + db << "SELECT * FROM foo" + >> [&](std::variant z) { + if(z.index() == 0) { + found_null = true; + } else { + found_string = true; + } + }; + REQUIRE((found_null && found_string)); + db << "SELECT NULL" >> v; + REQUIRE(v.index() == 0); +} +#endif