├── .gitignore ├── .travis.yml ├── .ycm_extra_conf.py ├── CMakeLists.txt ├── License.txt ├── README.md ├── cmake ├── .DS_Store ├── Catch.cmake ├── CatchAddTests.cmake ├── HunterGate.cmake └── ParseAndAddCatchTests.cmake ├── hdr ├── sqlite_modern_cpp.h └── sqlite_modern_cpp │ ├── errors.h │ ├── lists │ └── error_codes.h │ ├── log.h │ ├── sqlcipher.h │ ├── type_wrapper.h │ └── utility │ ├── function_traits.h │ ├── uncaught_exceptions.h │ └── utf16_utf8.h └── tests ├── blob_example.cc ├── error_log.cc ├── exception_dont_execute.cc ├── exception_dont_execute_nested.cc ├── exceptions.cc ├── flags.cc ├── functions.cc ├── functors.cc ├── lvalue_functor.cc ├── mov_ctor.cc ├── named.cc ├── nullptr_uniqueptr.cc ├── prepared_statment.cc ├── readme_example.cc ├── shared_connection.cc ├── simple_examples.cc ├── sqlcipher.cc ├── std_optional.cc ├── string_view.cc ├── trycatchblocks.cc └── variant.cc /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .deps 3 | 4 | config.log 5 | 6 | config.status 7 | compile_commands.json 8 | build/ 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | dist: trusty 4 | 5 | cache: 6 | apt: true 7 | directories: 8 | - /home/travis/.hunter/ 9 | 10 | addons: 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | - george-edison55-precise-backports 15 | packages: 16 | - g++-5 17 | - libsqlcipher-dev 18 | - cmake 19 | 20 | before_install: 21 | - export CXX="g++-5" CC="gcc-5" 22 | 23 | script: mkdir build && cd ./build && cmake .. && cmake --build . && ctest . 24 | 25 | # TODO: fix sqlcipher test 26 | # script: mkdir build && cd ./build && cmake -DENABLE_SQLCIPHER_TESTS=ON .. && make && ./tests 27 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | 4 | from clang_helpers import PrepareClangFlags 5 | 6 | def DirectoryOfThisScript(): 7 | return os.path.dirname(os.path.abspath(__file__)) 8 | 9 | # This is the single most important line in this script. Everything else is just nice to have but 10 | # not strictly necessary. 11 | compilation_database_folder = DirectoryOfThisScript() 12 | 13 | # This provides a safe fall-back if no compilation commands are available. You could also add a 14 | # includes relative to your project directory, for example. 15 | flags = [ 16 | '-Wall', 17 | '-std=c++14', 18 | '-x', 19 | 'c++', 20 | '-isystem', '/usr/local/include', 21 | '-isystem', '/usr/include', 22 | '-I.', 23 | ] 24 | 25 | database = ycm_core.CompilationDatabase(compilation_database_folder) 26 | 27 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 28 | 29 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 30 | if not working_directory: 31 | return list( flags ) 32 | new_flags = [] 33 | make_next_absolute = False 34 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 35 | for flag in flags: 36 | new_flag = flag 37 | 38 | if make_next_absolute: 39 | make_next_absolute = False 40 | if not flag.startswith( '/' ): 41 | new_flag = os.path.join( working_directory, flag ) 42 | 43 | for path_flag in path_flags: 44 | if flag == path_flag: 45 | make_next_absolute = True 46 | break 47 | 48 | if flag.startswith( path_flag ): 49 | path = flag[ len( path_flag ): ] 50 | new_flag = path_flag + os.path.join( working_directory, path ) 51 | break 52 | 53 | if new_flag: 54 | new_flags.append( new_flag ) 55 | return new_flags 56 | 57 | 58 | def IsHeaderFile( filename ): 59 | extension = os.path.splitext( filename )[ 1 ] 60 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 61 | 62 | 63 | def GetCompilationInfoForFile( filename ): 64 | # The compilation_commands.json file generated by CMake does not have entries 65 | # for header files. So we do our best by asking the db for flags for a 66 | # corresponding source file, if any. If one exists, the flags for that file 67 | # should be good enough. 68 | if IsHeaderFile( filename ): 69 | basename = os.path.splitext( filename )[ 0 ] 70 | for extension in SOURCE_EXTENSIONS: 71 | replacement_file = basename + extension 72 | if os.path.exists( replacement_file ): 73 | compilation_info = database.GetCompilationInfoForFile( 74 | replacement_file ) 75 | if compilation_info.compiler_flags_: 76 | return compilation_info 77 | return None 78 | return database.GetCompilationInfoForFile( filename ) 79 | 80 | 81 | def FlagsForFile( filename, **kwargs ): 82 | if database: 83 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 84 | # python list, but a "list-like" StringVec object 85 | compilation_info = GetCompilationInfoForFile( filename ) 86 | if not compilation_info: 87 | return None 88 | 89 | final_flags = MakeRelativePathsInFlagsAbsolute( 90 | compilation_info.compiler_flags_, 91 | compilation_info.compiler_working_dir_ ) 92 | 93 | else: 94 | relative_to = DirectoryOfThisScript() 95 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 96 | 97 | return { 98 | 'flags': final_flags, 99 | 'do_cache': True 100 | } 101 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | option(ENABLE_SQLCIPHER_TESTS "enable sqlchipher test") 3 | 4 | # Creates the file compile_commands.json in the build directory. 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") 9 | set(HUNTER_TLS_VERIFY ON) 10 | include("cmake/HunterGate.cmake") 11 | include("cmake/Catch.cmake") 12 | 13 | HunterGate( 14 | URL "https://github.com/cpp-pm/hunter/archive/v0.24.15.tar.gz" 15 | SHA1 "8010d63d5ae611c564889d5fe12d3cb7a45703ac" 16 | ) 17 | 18 | project(SqliteModernCpp) 19 | 20 | hunter_add_package(Catch) 21 | hunter_add_package(sqlite3) 22 | 23 | find_package(Catch2 CONFIG REQUIRED) 24 | find_package(sqlite3 CONFIG REQUIRED) 25 | 26 | set(TEST_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) 27 | file(GLOB TEST_SOURCES ${TEST_SOURCE_DIR}/*.cc) 28 | 29 | IF(NOT ENABLE_SQLCIPHER_TESTS) 30 | list(REMOVE_ITEM TEST_SOURCES ${TEST_SOURCE_DIR}/sqlcipher.cc) 31 | ENDIF(NOT ENABLE_SQLCIPHER_TESTS) 32 | 33 | enable_testing() 34 | 35 | add_library (sqlite_modern_cpp INTERFACE) 36 | target_include_directories(sqlite_modern_cpp INTERFACE hdr/) 37 | 38 | add_executable(tests_runner ${TEST_SOURCES}) 39 | target_include_directories(tests_runner INTERFACE ${SQLITE3_INCLUDE_DIRS}) 40 | if(ENABLE_SQLCIPHER_TESTS) 41 | target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3 -lsqlcipher) 42 | else() 43 | target_link_libraries(tests_runner Catch2::Catch2 sqlite_modern_cpp sqlite3::sqlite3) 44 | endif() 45 | 46 | catch_discover_tests(tests_runner) 47 | target_compile_options(tests_runner PUBLIC $<$:/Zc:__cplusplus> ) 48 | 49 | # Place the file in the source directory, permitting us to place a single configuration file for YCM there. 50 | # YCM is the code-completion engine for (neo)vim https://github.com/Valloric/YouCompleteMe 51 | IF(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json") 52 | EXECUTE_PROCESS( COMMAND ${CMAKE_COMMAND} -E copy_if_different 53 | ${CMAKE_BINARY_DIR}/compile_commands.json 54 | ${CMAKE_SOURCE_DIR}/compile_commands.json 55 | ) 56 | ENDIF() 57 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 aminroosta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/status.png)](https://drone.io/github.com/aminroosta/sqlite_modern_cpp/latest) 2 | 3 | sqlite modern cpp wrapper 4 | ==== 5 | 6 | This library is a lightweight modern wrapper around sqlite C api . 7 | 8 | ```c++ 9 | #include 10 | #include 11 | using namespace sqlite; 12 | using namespace std; 13 | 14 | int main() { 15 | 16 | try { 17 | // creates a database file 'dbfile.db' if it does not exists. 18 | database db("dbfile.db"); 19 | 20 | // executes the query and creates a 'user' table 21 | db << 22 | "create table if not exists user (" 23 | " _id integer primary key autoincrement not null," 24 | " age int," 25 | " name text," 26 | " weight real" 27 | ");"; 28 | 29 | // inserts a new user record. 30 | // binds the fields to '?' . 31 | // note that only types allowed for bindings are : 32 | // int ,long, long long, float, double 33 | // string, u16string 34 | // sqlite3 only supports utf8 and utf16 strings, you should use std::string for utf8 and std::u16string for utf16. 35 | // If you're using C++17, `std::string_view` and `std::u16string_view` can be used as string types. 36 | // note that u"my text" is a utf16 string literal of type char16_t * . 37 | db << "insert into user (age,name,weight) values (?,?,?);" 38 | << 20 39 | << u"bob" 40 | << 83.25; 41 | 42 | int age = 21; 43 | float weight = 68.5; 44 | string name = "jack"; 45 | db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string 46 | << age 47 | << name 48 | << weight; 49 | 50 | cout << "The new record got assigned id " << db.last_insert_rowid() << endl; 51 | 52 | // selects from user table on a condition ( age > 18 ) and executes 53 | // the lambda for each row returned . 54 | db << "select age,name,weight from user where age > ? ;" 55 | << 18 56 | >> [&](int age, string name, double weight) { 57 | cout << age << ' ' << name << ' ' << weight << endl; 58 | }; 59 | 60 | // a for loop can be used too: 61 | // with named variables 62 | for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 18) { 63 | int age; string name; double weight; 64 | row >> age >> name >> weight; 65 | cout << age << ' ' << name << ' ' << weight << endl; 66 | } 67 | // or with a tuple 68 | for(tuple row : db << "select age,name,weight from user where age > ? ;" << 18) { 69 | cout << get<0>(row) << ' ' << get<1>(row) << ' ' << get<2>(row) << endl; 70 | } 71 | 72 | // selects the count(*) from user table 73 | // note that you can extract a single column single row result only to : int,long,long long,float,double,string,u16string 74 | int count = 0; 75 | db << "select count(*) from user" >> count; 76 | cout << "cout : " << count << endl; 77 | 78 | // you can also extract multiple column rows 79 | db << "select age, name from user where _id=1;" >> tie(age, name); 80 | cout << "Age = " << age << ", name = " << name << endl; 81 | 82 | // this also works and the returned value will be automatically converted to string 83 | string str_count; 84 | db << "select count(*) from user" >> str_count; 85 | cout << "scount : " << str_count << endl; 86 | } 87 | catch (const exception& e) { 88 | cout << e.what() << endl; 89 | } 90 | } 91 | ``` 92 | 93 | You can not execute multiple statements separated by semicolons in one go. 94 | 95 | Additional flags 96 | ---- 97 | You can pass additional open flags to SQLite by using a config object: 98 | 99 | ```c++ 100 | sqlite_config config; 101 | config.flags = OpenFlags::READONLY 102 | database db("some_db", config); 103 | int a; 104 | // Now you can only read from db 105 | auto ps = db << "select a from table where something = ? and anotherthing = ?" >> a; 106 | config.flags = OpenFlags::READWRITE | OpenFlags::CREATE; // This is the default 107 | config.encoding = Encoding::UTF16; // The encoding is respected only if you create a new database 108 | database db2("some_db2", config); 109 | // If some_db2 didn't exists before, it will be created with UTF-16 encoding. 110 | ``` 111 | 112 | Prepared Statements 113 | ---- 114 | 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. 115 | 116 | ```c++ 117 | database db(":memory:"); 118 | 119 | // if you use << on a sqlite::database you get a prepared statment back 120 | // this will not be executed till it gets destroyed or you execute it explicitly 121 | auto ps = db << "select a,b from table where something = ? and anotherthing = ?"; // get a prepared parsed and ready statment 122 | 123 | // first if needed bind values to it 124 | ps << 5; 125 | int tmp = 8; 126 | ps << tmp; 127 | 128 | // now you can execute it with `operator>>` or `execute()`. 129 | // If the statement was executed once it will not be executed again when it goes out of scope. 130 | // But beware that it will execute on destruction if it wasn't executed! 131 | ps >> [&](int a,int b){ ... }; 132 | 133 | // after a successfull execution the statment can be executed again, but the bound values are resetted. 134 | // If you dont need the returned values you can execute it like this 135 | ps.execute(); 136 | // or like this 137 | ps++; 138 | 139 | // To disable the execution of a statment when it goes out of scope and wasn't used 140 | ps.used(true); // or false if you want it to execute even if it was used 141 | 142 | // Usage Example: 143 | 144 | auto ps = db << "insert into complex_table_with_lots_of_indices values (?,?,?)"; 145 | int i = 0; 146 | while( i < 100000 ){ 147 | ps << long_list[i++] << long_list[i++] << long_list[i++]; 148 | ps++; 149 | } 150 | ``` 151 | 152 | Shared Connections 153 | ---- 154 | 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. 155 | 156 | Take this example on how to deal with a database backup using SQLITEs own functions in a safe and modern way. 157 | ```c++ 158 | try { 159 | database backup("backup"); //Open the database file we want to backup to 160 | 161 | auto con = db.connection(); // get a handle to the DB we want to backup in our scope 162 | // this way we are sure the DB is open and ok while we backup 163 | 164 | // Init Backup and make sure its freed on exit or exceptions! 165 | auto state = 166 | std::unique_ptr( 167 | sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"), 168 | sqlite3_backup_finish 169 | ); 170 | 171 | if(state) { 172 | int rc; 173 | // Each iteration of this loop copies 500 database pages from database db to the backup database. 174 | do { 175 | rc = sqlite3_backup_step(state.get(), 500); 176 | std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n"; 177 | } while(rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); 178 | } 179 | } // Release allocated resources. 180 | ``` 181 | 182 | Transactions 183 | ---- 184 | You can use transactions with `begin;`, `commit;` and `rollback;` commands. 185 | 186 | ```c++ 187 | db << "begin;"; // begin a transaction ... 188 | db << "insert into user (age,name,weight) values (?,?,?);" 189 | << 20 190 | << u"bob" 191 | << 83.25f; 192 | db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string 193 | << 21 194 | << u"jack" 195 | << 68.5; 196 | db << "commit;"; // commit all the changes. 197 | 198 | db << "begin;"; // begin another transaction .... 199 | db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string 200 | << 19 201 | << u"chirs" 202 | << 82.7; 203 | db << "rollback;"; // cancel this transaction ... 204 | 205 | ``` 206 | 207 | Blob 208 | ---- 209 | Use `std::vector` to store and retrieve blob data. 210 | `T` could be `char,short,int,long,long long, float or double`. 211 | 212 | ```c++ 213 | db << "CREATE TABLE person (name TEXT, numbers BLOB);"; 214 | db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; 215 | db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; 216 | 217 | vector numbers_bob; 218 | db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; 219 | 220 | db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector numbers_sara){ 221 | for(auto e : numbers_sara) cout << e << ' '; cout << endl; 222 | }; 223 | ``` 224 | 225 | NULL values 226 | ---- 227 | 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. 228 | 229 | ```c++ 230 | db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);"; 231 | db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector { 1, 2 , 3}; 232 | unique_ptr ptr_null; // you can even bind empty unique_ptr 233 | db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr; 234 | 235 | db << "select age,name,img from tbl where id = 1" 236 | >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { 237 | if(age_p == nullptr || name_p == nullptr || img_p == nullptr) { 238 | cerr << "ERROR: values should not be null" << std::endl; 239 | } 240 | 241 | cout << "age:" << *age_p << " name:" << *name_p << " img:"; 242 | for(auto i : *img_p) cout << i << ","; cout << endl; 243 | }; 244 | 245 | db << "select age,name,img from tbl where id = 2" 246 | >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { 247 | if(age_p != nullptr || name_p != nullptr || img_p != nullptr) { 248 | cerr << "ERROR: values should be nullptr" << std::endl; 249 | exit(EXIT_FAILURE); 250 | } 251 | 252 | cout << "OK all three values are nullptr" << endl; 253 | }; 254 | ``` 255 | 256 | SQLCipher 257 | ---- 258 | 259 | We have native support for [SQLCipher](https://www.zetetic.net/sqlcipher/). 260 | If you want to use encrypted databases, include the `sqlite_moder_cpp/sqlcipher.h` header. 261 | Then create a `sqlcipher_database` instead. 262 | 263 | ```c++ 264 | #include 265 | #include 266 | using namespace sqlite; 267 | using namespace std; 268 | 269 | int main() { 270 | try { 271 | // creates a database file 'dbfile.db' if it does not exists with password 'secret' 272 | sqlcipher_config config; 273 | config.key = secret; 274 | sqlcipher_database db("dbfile.db", config); 275 | 276 | // executes the query and creates a 'user' table 277 | db << 278 | "create table if not exists user (" 279 | " _id integer primary key autoincrement not null," 280 | " age int," 281 | " name text," 282 | " weight real" 283 | ");"; 284 | 285 | // More queries ... 286 | db.rekey("new_secret"); // Change the password of the already encrypted database. 287 | 288 | // Even more queries .. 289 | } 290 | catch (const exception& e) { cout << e.what() << endl; } 291 | } 292 | ``` 293 | 294 | NULL values (C++17) 295 | ---- 296 | You can use `std::optional` as an alternative for `std::unique_ptr` to work with NULL values. 297 | 298 | ```c++ 299 | #include 300 | 301 | struct User { 302 | long long _id; 303 | std::optional age; 304 | std::optional name; 305 | std::optional weight; 306 | }; 307 | 308 | int main() { 309 | User user; 310 | user.name = "bob"; 311 | 312 | // Same database as above 313 | database db("dbfile.db"); 314 | 315 | // Here, age and weight will be inserted as NULL in the database. 316 | db << "insert into user (age,name,weight) values (?,?,?);" 317 | << user.age 318 | << user.name 319 | << user.weight; 320 | user._id = db.last_insert_rowid(); 321 | 322 | // Here, the User instance will retain the NULL value(s) from the database. 323 | db << "select _id,age,name,weight from user where age > ? ;" 324 | << 18 325 | >> [&](long long id, 326 | std::optional age, 327 | std::optional name 328 | std::optional weight) { 329 | 330 | cout << "id=" << _id 331 | << " age = " << (age ? to_string(*age) ? string("NULL")) 332 | << " name = " << (name ? *name : string("NULL")) 333 | << " weight = " << (weight ? to_string(*weight) : string(NULL)) 334 | << endl; 335 | }; 336 | } 337 | ``` 338 | If the optional library is not available, the experimental/optional one will be used instead. 339 | 340 | Variant type support (C++17) 341 | ---- 342 | If your columns may have flexible types, you can use C++17's `std::variant` to extract the value. 343 | 344 | ```c++ 345 | db << "CREATE TABLE tbl (id integer, data);"; 346 | db << "INSERT INTO tbl VALUES (?, ?);" << 1 << vector { 1, 2, 3}; 347 | db << "INSERT INTO tbl VALUES (?, ?);" << 2 << 2.5; 348 | 349 | db << "select data from tbl where id = 1" 350 | >> [](std::variant, double> data) { 351 | if(data.index() != 1) { 352 | cerr << "ERROR: we expected a blob" << std::endl; 353 | } 354 | 355 | for(auto i : get>(data)) cout << i << ","; cout << endl; 356 | }; 357 | 358 | db << "select data from tbl where id = 2" 359 | >> [](std::variant, double> data) { 360 | if(data.index() != 2) { 361 | cerr << "ERROR: we expected a real number" << std::endl; 362 | } 363 | 364 | cout << get(data) << endl; 365 | }; 366 | ``` 367 | 368 | If you read a specific type and this type does not match the actual type in the SQlite database, yor data will be converted. 369 | This does not happen if you use a `variant`. 370 | If the `variant` does an alternative of the same value type, an `mismatch` exception will be thrown. 371 | The value types are NULL, integer, real number, text and BLOB. 372 | To support all possible values, you can use `variant`. 373 | It is also possible to use a variant with `std::monostate` in order to catch null values. 374 | 375 | Errors 376 | ---- 377 | 378 | 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. 379 | 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. 380 | Additionally you can use `get_sql()` to see the SQL statement leading to the error. 381 | 382 | ```c++ 383 | database db(":memory:"); 384 | db << "create table person (id integer primary key not null, name text);"; 385 | 386 | try { 387 | db << "insert into person (id, name) values (?,?)" << 1 << "jack"; 388 | // inserting again to produce error 389 | db << "insert into person (id, name) values (?,?)" << 1 << "jack"; 390 | } 391 | /* if you are trying to catch all sqlite related exceptions 392 | * make sure to catch them by reference */ 393 | catch (const sqlite_exception& e) { 394 | cerr << e.get_code() << ": " << e.what() << " during " 395 | << e.get_sql() << endl; 396 | } 397 | /* you can catch specific exceptions as well, 398 | catch(const sqlite::errors::constraint &e) { } */ 399 | /* and even more specific exceptions 400 | catch(const sqlite::errors::constraint_primarykey &e) { } */ 401 | ``` 402 | 403 | You can also register a error logging function with `sqlite::error_log`. 404 | The `` header has to be included to make this function available. 405 | The call to `sqlite::error_log` has to be the first call to any `sqlite_modern_cpp` function by your program. 406 | 407 | ```c++ 408 | error_log( 409 | [&](sqlite_exception& e) { 410 | cerr << e.get_code() << ": " << e.what() << endl; 411 | }, 412 | [&](errors::misuse& e) { 413 | /* You can behave differently to specific errors */ 414 | } 415 | ); 416 | database db(":memory:"); 417 | db << "create table person (id integer primary key not null, name text);"; 418 | 419 | try { 420 | db << "insert into person (id, name) values (?,?)" << 1 << "jack"; 421 | // inserting again to produce error 422 | db << "insert into person (id, name) values (?,?)" << 1 << "jack"; 423 | } 424 | catch (const sqlite_exception& e) {} 425 | ``` 426 | 427 | Custom SQL functions 428 | ---- 429 | 430 | To extend SQLite with custom functions, you just implement them in C++: 431 | 432 | ```c++ 433 | database db(":memory:"); 434 | db.define("tgamma", [](double i) {return std::tgamma(i);}); 435 | db << "CREATE TABLE numbers (number INTEGER);"; 436 | 437 | for(auto i=0; i!=10; ++i) 438 | db << "INSERT INTO numbers VALUES (?);" << i; 439 | 440 | db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) { 441 | cout << number << "! = " << factorial << '\n'; 442 | }; 443 | ``` 444 | 445 | NDK support 446 | ---- 447 | Just Make sure you are using the full path of your database file : 448 | `sqlite::database db("/data/data/com.your.package/dbfile.db")`. 449 | 450 | Installation 451 | ---- 452 | The project is header only. 453 | Simply point your compiler at the hdr/ directory. 454 | 455 | Contributing 456 | ---- 457 | Install cmake and build the project. 458 | Dependencies will be installed automatically (using hunter). 459 | 460 | ```bash 461 | mkdir build 462 | cd ./build 463 | cmake .. 464 | make 465 | ``` 466 | 467 | Breaking Changes 468 | ---- 469 | See breaking changes documented in each [Release](https://github.com/aminroosta/sqlite_modern_cpp/releases). 470 | 471 | Package managers 472 | ---- 473 | Pull requests are welcome :wink: 474 | - [AUR](https://aur.archlinux.org/packages/sqlite_modern_cpp/) Arch Linux 475 | - maintainer [Nissar Chababy](https://github.com/funilrys) 476 | - Nuget (TODO [nuget.org](https://www.nuget.org/)) 477 | - Conan (TODO [conan.io](https://conan.io/)) 478 | - [vcpkg](https://github.com/Microsoft/vcpkg) 479 | 480 | ## License 481 | 482 | MIT license - [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) 483 | -------------------------------------------------------------------------------- /cmake/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SqliteModernCpp/sqlite_modern_cpp/6e3009973025e0016d5573529067714201338c80/cmake/.DS_Store -------------------------------------------------------------------------------- /cmake/Catch.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #[=======================================================================[.rst: 5 | Catch 6 | ----- 7 | 8 | This module defines a function to help use the Catch test framework. 9 | 10 | The :command:`catch_discover_tests` discovers tests by asking the compiled test 11 | executable to enumerate its tests. This does not require CMake to be re-run 12 | when tests change. However, it may not work in a cross-compiling environment, 13 | and setting test properties is less convenient. 14 | 15 | This command is intended to replace use of :command:`add_test` to register 16 | tests, and will create a separate CTest test for each Catch test case. Note 17 | that this is in some cases less efficient, as common set-up and tear-down logic 18 | cannot be shared by multiple test cases executing in the same instance. 19 | However, it provides more fine-grained pass/fail information to CTest, which is 20 | usually considered as more beneficial. By default, the CTest test name is the 21 | same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. 22 | 23 | .. command:: catch_discover_tests 24 | 25 | Automatically add tests with CTest by querying the compiled test executable 26 | for available tests:: 27 | 28 | catch_discover_tests(target 29 | [TEST_SPEC arg1...] 30 | [EXTRA_ARGS arg1...] 31 | [WORKING_DIRECTORY dir] 32 | [TEST_PREFIX prefix] 33 | [TEST_SUFFIX suffix] 34 | [PROPERTIES name1 value1...] 35 | [TEST_LIST var] 36 | ) 37 | 38 | ``catch_discover_tests`` sets up a post-build command on the test executable 39 | that generates the list of tests by parsing the output from running the test 40 | with the ``--list-test-names-only`` argument. This ensures that the full 41 | list of tests is obtained. Since test discovery occurs at build time, it is 42 | not necessary to re-run CMake when the list of tests changes. 43 | However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set 44 | in order to function in a cross-compiling environment. 45 | 46 | Additionally, setting properties on tests is somewhat less convenient, since 47 | the tests are not available at CMake time. Additional test properties may be 48 | assigned to the set of tests as a whole using the ``PROPERTIES`` option. If 49 | more fine-grained test control is needed, custom content may be provided 50 | through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` 51 | directory property. The set of discovered tests is made accessible to such a 52 | script via the ``_TESTS`` variable. 53 | 54 | The options are: 55 | 56 | ``target`` 57 | Specifies the Catch executable, which must be a known CMake executable 58 | target. CMake will substitute the location of the built executable when 59 | running the test. 60 | 61 | ``TEST_SPEC arg1...`` 62 | Specifies test cases, wildcarded test cases, tags and tag expressions to 63 | pass to the Catch executable with the ``--list-test-names-only`` argument. 64 | 65 | ``EXTRA_ARGS arg1...`` 66 | Any extra arguments to pass on the command line to each test case. 67 | 68 | ``WORKING_DIRECTORY dir`` 69 | Specifies the directory in which to run the discovered test cases. If this 70 | option is not provided, the current binary directory is used. 71 | 72 | ``TEST_PREFIX prefix`` 73 | Specifies a ``prefix`` to be prepended to the name of each discovered test 74 | case. This can be useful when the same test executable is being used in 75 | multiple calls to ``catch_discover_tests()`` but with different 76 | ``TEST_SPEC`` or ``EXTRA_ARGS``. 77 | 78 | ``TEST_SUFFIX suffix`` 79 | Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of 80 | every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may 81 | be specified. 82 | 83 | ``PROPERTIES name1 value1...`` 84 | Specifies additional properties to be set on all tests discovered by this 85 | invocation of ``catch_discover_tests``. 86 | 87 | ``TEST_LIST var`` 88 | Make the list of tests available in the variable ``var``, rather than the 89 | default ``_TESTS``. This can be useful when the same test 90 | executable is being used in multiple calls to ``catch_discover_tests()``. 91 | Note that this variable is only available in CTest. 92 | 93 | #]=======================================================================] 94 | 95 | #------------------------------------------------------------------------------ 96 | function(catch_discover_tests TARGET) 97 | cmake_parse_arguments( 98 | "" 99 | "" 100 | "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" 101 | "TEST_SPEC;EXTRA_ARGS;PROPERTIES" 102 | ${ARGN} 103 | ) 104 | 105 | if(NOT _WORKING_DIRECTORY) 106 | set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") 107 | endif() 108 | if(NOT _TEST_LIST) 109 | set(_TEST_LIST ${TARGET}_TESTS) 110 | endif() 111 | 112 | ## Generate a unique name based on the extra arguments 113 | string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") 114 | string(SUBSTRING ${args_hash} 0 7 args_hash) 115 | 116 | # Define rule to generate test list for aforementioned test executable 117 | set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") 118 | set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") 119 | get_property(crosscompiling_emulator 120 | TARGET ${TARGET} 121 | PROPERTY CROSSCOMPILING_EMULATOR 122 | ) 123 | add_custom_command( 124 | TARGET ${TARGET} POST_BUILD 125 | BYPRODUCTS "${ctest_tests_file}" 126 | COMMAND "${CMAKE_COMMAND}" 127 | -D "TEST_TARGET=${TARGET}" 128 | -D "TEST_EXECUTABLE=$" 129 | -D "TEST_EXECUTOR=${crosscompiling_emulator}" 130 | -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" 131 | -D "TEST_SPEC=${_TEST_SPEC}" 132 | -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" 133 | -D "TEST_PROPERTIES=${_PROPERTIES}" 134 | -D "TEST_PREFIX=${_TEST_PREFIX}" 135 | -D "TEST_SUFFIX=${_TEST_SUFFIX}" 136 | -D "TEST_LIST=${_TEST_LIST}" 137 | -D "CTEST_FILE=${ctest_tests_file}" 138 | -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" 139 | VERBATIM 140 | ) 141 | 142 | file(WRITE "${ctest_include_file}" 143 | "if(EXISTS \"${ctest_tests_file}\")\n" 144 | " include(\"${ctest_tests_file}\")\n" 145 | "else()\n" 146 | " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" 147 | "endif()\n" 148 | ) 149 | 150 | if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") 151 | # Add discovered tests to directory TEST_INCLUDE_FILES 152 | set_property(DIRECTORY 153 | APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" 154 | ) 155 | else() 156 | # Add discovered tests as directory TEST_INCLUDE_FILE if possible 157 | get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) 158 | if (NOT ${test_include_file_set}) 159 | set_property(DIRECTORY 160 | PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" 161 | ) 162 | else() 163 | message(FATAL_ERROR 164 | "Cannot set more than one TEST_INCLUDE_FILE" 165 | ) 166 | endif() 167 | endif() 168 | 169 | endfunction() 170 | 171 | ############################################################################### 172 | 173 | set(_CATCH_DISCOVER_TESTS_SCRIPT 174 | ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake 175 | ) 176 | -------------------------------------------------------------------------------- /cmake/CatchAddTests.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | set(prefix "${TEST_PREFIX}") 5 | set(suffix "${TEST_SUFFIX}") 6 | set(spec ${TEST_SPEC}) 7 | set(extra_args ${TEST_EXTRA_ARGS}) 8 | set(properties ${TEST_PROPERTIES}) 9 | set(script) 10 | set(suite) 11 | set(tests) 12 | 13 | function(add_command NAME) 14 | set(_args "") 15 | foreach(_arg ${ARGN}) 16 | if(_arg MATCHES "[^-./:a-zA-Z0-9_]") 17 | set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument 18 | else() 19 | set(_args "${_args} ${_arg}") 20 | endif() 21 | endforeach() 22 | set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) 23 | endfunction() 24 | 25 | # Run test executable to get list of available tests 26 | if(NOT EXISTS "${TEST_EXECUTABLE}") 27 | message(FATAL_ERROR 28 | "Specified test executable '${TEST_EXECUTABLE}' does not exist" 29 | ) 30 | endif() 31 | execute_process( 32 | COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only 33 | OUTPUT_VARIABLE output 34 | RESULT_VARIABLE result 35 | ) 36 | # Catch --list-test-names-only reports the number of tests, so 0 is... surprising 37 | if(${result} EQUAL 0) 38 | message(WARNING 39 | "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" 40 | ) 41 | elseif(${result} LESS 0) 42 | message(FATAL_ERROR 43 | "Error running test executable '${TEST_EXECUTABLE}':\n" 44 | " Result: ${result}\n" 45 | " Output: ${output}\n" 46 | ) 47 | endif() 48 | 49 | string(REPLACE "\n" ";" output "${output}") 50 | 51 | # Parse output 52 | foreach(line ${output}) 53 | # Test name; strip spaces to get just the name... 54 | string(REGEX REPLACE "^ +" "" test "${line}") 55 | # ...and add to script 56 | add_command(add_test 57 | "${prefix}${test}${suffix}" 58 | ${TEST_EXECUTOR} 59 | "${TEST_EXECUTABLE}" 60 | "${test}" 61 | ${extra_args} 62 | ) 63 | add_command(set_tests_properties 64 | "${prefix}${test}${suffix}" 65 | PROPERTIES 66 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 67 | ${properties} 68 | ) 69 | list(APPEND tests "${prefix}${test}${suffix}") 70 | endforeach() 71 | 72 | # Create a list of all discovered tests, which users may use to e.g. set 73 | # properties on the tests 74 | add_command(set ${TEST_LIST} ${tests}) 75 | 76 | # Write CTest script 77 | file(WRITE "${CTEST_FILE}" "${script}") 78 | -------------------------------------------------------------------------------- /cmake/HunterGate.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017, Ruslan Baratov 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | # This is a gate file to Hunter package manager. 26 | # Include this file using `include` command and add package you need, example: 27 | # 28 | # cmake_minimum_required(VERSION 3.0) 29 | # 30 | # include("cmake/HunterGate.cmake") 31 | # HunterGate( 32 | # URL "https://github.com/path/to/hunter/archive.tar.gz" 33 | # SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" 34 | # ) 35 | # 36 | # project(MyProject) 37 | # 38 | # hunter_add_package(Foo) 39 | # hunter_add_package(Boo COMPONENTS Bar Baz) 40 | # 41 | # Projects: 42 | # * https://github.com/hunter-packages/gate/ 43 | # * https://github.com/ruslo/hunter 44 | 45 | option(HUNTER_ENABLED "Enable Hunter package manager support" ON) 46 | if(HUNTER_ENABLED) 47 | if(CMAKE_VERSION VERSION_LESS "3.0") 48 | message(FATAL_ERROR "At least CMake version 3.0 required for hunter dependency management." 49 | " Update CMake or set HUNTER_ENABLED to OFF.") 50 | endif() 51 | endif() 52 | 53 | include(CMakeParseArguments) # cmake_parse_arguments 54 | 55 | option(HUNTER_STATUS_PRINT "Print working status" ON) 56 | option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) 57 | 58 | set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki") 59 | 60 | function(hunter_gate_status_print) 61 | foreach(print_message ${ARGV}) 62 | if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) 63 | message(STATUS "[hunter] ${print_message}") 64 | endif() 65 | endforeach() 66 | endfunction() 67 | 68 | function(hunter_gate_status_debug) 69 | foreach(print_message ${ARGV}) 70 | if(HUNTER_STATUS_DEBUG) 71 | string(TIMESTAMP timestamp) 72 | message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") 73 | endif() 74 | endforeach() 75 | endfunction() 76 | 77 | function(hunter_gate_wiki wiki_page) 78 | message("------------------------------ WIKI -------------------------------") 79 | message(" ${HUNTER_WIKI}/${wiki_page}") 80 | message("-------------------------------------------------------------------") 81 | message("") 82 | message(FATAL_ERROR "") 83 | endfunction() 84 | 85 | function(hunter_gate_internal_error) 86 | message("") 87 | foreach(print_message ${ARGV}) 88 | message("[hunter ** INTERNAL **] ${print_message}") 89 | endforeach() 90 | message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") 91 | message("") 92 | hunter_gate_wiki("error.internal") 93 | endfunction() 94 | 95 | function(hunter_gate_fatal_error) 96 | cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}") 97 | string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki) 98 | if(have_no_wiki) 99 | hunter_gate_internal_error("Expected wiki") 100 | endif() 101 | message("") 102 | foreach(x ${hunter_UNPARSED_ARGUMENTS}) 103 | message("[hunter ** FATAL ERROR **] ${x}") 104 | endforeach() 105 | message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") 106 | message("") 107 | hunter_gate_wiki("${hunter_WIKI}") 108 | endfunction() 109 | 110 | function(hunter_gate_user_error) 111 | hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data") 112 | endfunction() 113 | 114 | function(hunter_gate_self root version sha1 result) 115 | string(COMPARE EQUAL "${root}" "" is_bad) 116 | if(is_bad) 117 | hunter_gate_internal_error("root is empty") 118 | endif() 119 | 120 | string(COMPARE EQUAL "${version}" "" is_bad) 121 | if(is_bad) 122 | hunter_gate_internal_error("version is empty") 123 | endif() 124 | 125 | string(COMPARE EQUAL "${sha1}" "" is_bad) 126 | if(is_bad) 127 | hunter_gate_internal_error("sha1 is empty") 128 | endif() 129 | 130 | string(SUBSTRING "${sha1}" 0 7 archive_id) 131 | 132 | if(EXISTS "${root}/cmake/Hunter") 133 | set(hunter_self "${root}") 134 | else() 135 | set( 136 | hunter_self 137 | "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" 138 | ) 139 | endif() 140 | 141 | set("${result}" "${hunter_self}" PARENT_SCOPE) 142 | endfunction() 143 | 144 | # Set HUNTER_GATE_ROOT cmake variable to suitable value. 145 | function(hunter_gate_detect_root) 146 | # Check CMake variable 147 | string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) 148 | if(not_empty) 149 | set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) 150 | hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") 151 | return() 152 | endif() 153 | 154 | # Check environment variable 155 | string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) 156 | if(not_empty) 157 | set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) 158 | hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") 159 | return() 160 | endif() 161 | 162 | # Check HOME environment variable 163 | string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) 164 | if(result) 165 | set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) 166 | hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") 167 | return() 168 | endif() 169 | 170 | # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) 171 | if(WIN32) 172 | string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) 173 | if(result) 174 | set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) 175 | hunter_gate_status_debug( 176 | "HUNTER_ROOT set using SYSTEMDRIVE environment variable" 177 | ) 178 | return() 179 | endif() 180 | 181 | string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) 182 | if(result) 183 | set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) 184 | hunter_gate_status_debug( 185 | "HUNTER_ROOT set using USERPROFILE environment variable" 186 | ) 187 | return() 188 | endif() 189 | endif() 190 | 191 | hunter_gate_fatal_error( 192 | "Can't detect HUNTER_ROOT" 193 | WIKI "error.detect.hunter.root" 194 | ) 195 | endfunction() 196 | 197 | macro(hunter_gate_lock dir) 198 | if(NOT HUNTER_SKIP_LOCK) 199 | if("${CMAKE_VERSION}" VERSION_LESS "3.2") 200 | hunter_gate_fatal_error( 201 | "Can't lock, upgrade to CMake 3.2 or use HUNTER_SKIP_LOCK" 202 | WIKI "error.can.not.lock" 203 | ) 204 | endif() 205 | hunter_gate_status_debug("Locking directory: ${dir}") 206 | file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) 207 | hunter_gate_status_debug("Lock done") 208 | endif() 209 | endmacro() 210 | 211 | function(hunter_gate_download dir) 212 | string( 213 | COMPARE 214 | NOTEQUAL 215 | "$ENV{HUNTER_DISABLE_AUTOINSTALL}" 216 | "" 217 | disable_autoinstall 218 | ) 219 | if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) 220 | hunter_gate_fatal_error( 221 | "Hunter not found in '${dir}'" 222 | "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" 223 | "Settings:" 224 | " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" 225 | " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" 226 | WIKI "error.run.install" 227 | ) 228 | endif() 229 | string(COMPARE EQUAL "${dir}" "" is_bad) 230 | if(is_bad) 231 | hunter_gate_internal_error("Empty 'dir' argument") 232 | endif() 233 | 234 | string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) 235 | if(is_bad) 236 | hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") 237 | endif() 238 | 239 | string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) 240 | if(is_bad) 241 | hunter_gate_internal_error("HUNTER_GATE_URL empty") 242 | endif() 243 | 244 | set(done_location "${dir}/DONE") 245 | set(sha1_location "${dir}/SHA1") 246 | 247 | set(build_dir "${dir}/Build") 248 | set(cmakelists "${dir}/CMakeLists.txt") 249 | 250 | hunter_gate_lock("${dir}") 251 | if(EXISTS "${done_location}") 252 | # while waiting for lock other instance can do all the job 253 | hunter_gate_status_debug("File '${done_location}' found, skip install") 254 | return() 255 | endif() 256 | 257 | file(REMOVE_RECURSE "${build_dir}") 258 | file(REMOVE_RECURSE "${cmakelists}") 259 | 260 | file(MAKE_DIRECTORY "${build_dir}") # check directory permissions 261 | 262 | # Disabling languages speeds up a little bit, reduces noise in the output 263 | # and avoids path too long windows error 264 | file( 265 | WRITE 266 | "${cmakelists}" 267 | "cmake_minimum_required(VERSION 3.0)\n" 268 | "project(HunterDownload LANGUAGES NONE)\n" 269 | "include(ExternalProject)\n" 270 | "ExternalProject_Add(\n" 271 | " Hunter\n" 272 | " URL\n" 273 | " \"${HUNTER_GATE_URL}\"\n" 274 | " URL_HASH\n" 275 | " SHA1=${HUNTER_GATE_SHA1}\n" 276 | " DOWNLOAD_DIR\n" 277 | " \"${dir}\"\n" 278 | " TLS_VERIFY\n" 279 | " ON\n" 280 | " SOURCE_DIR\n" 281 | " \"${dir}/Unpacked\"\n" 282 | " CONFIGURE_COMMAND\n" 283 | " \"\"\n" 284 | " BUILD_COMMAND\n" 285 | " \"\"\n" 286 | " INSTALL_COMMAND\n" 287 | " \"\"\n" 288 | ")\n" 289 | ) 290 | 291 | if(HUNTER_STATUS_DEBUG) 292 | set(logging_params "") 293 | else() 294 | set(logging_params OUTPUT_QUIET) 295 | endif() 296 | 297 | hunter_gate_status_debug("Run generate") 298 | 299 | # Need to add toolchain file too. 300 | # Otherwise on Visual Studio + MDD this will fail with error: 301 | # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" 302 | if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") 303 | get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) 304 | set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") 305 | else() 306 | # 'toolchain_arg' can't be empty 307 | set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") 308 | endif() 309 | 310 | string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) 311 | if(no_make) 312 | set(make_arg "") 313 | else() 314 | # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM 315 | set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") 316 | endif() 317 | 318 | execute_process( 319 | COMMAND 320 | "${CMAKE_COMMAND}" 321 | "-H${dir}" 322 | "-B${build_dir}" 323 | "-G${CMAKE_GENERATOR}" 324 | "${toolchain_arg}" 325 | ${make_arg} 326 | WORKING_DIRECTORY "${dir}" 327 | RESULT_VARIABLE download_result 328 | ${logging_params} 329 | ) 330 | 331 | if(NOT download_result EQUAL 0) 332 | hunter_gate_internal_error("Configure project failed") 333 | endif() 334 | 335 | hunter_gate_status_print( 336 | "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" 337 | " ${HUNTER_GATE_URL}" 338 | " -> ${dir}" 339 | ) 340 | execute_process( 341 | COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" 342 | WORKING_DIRECTORY "${dir}" 343 | RESULT_VARIABLE download_result 344 | ${logging_params} 345 | ) 346 | 347 | if(NOT download_result EQUAL 0) 348 | hunter_gate_internal_error("Build project failed") 349 | endif() 350 | 351 | file(REMOVE_RECURSE "${build_dir}") 352 | file(REMOVE_RECURSE "${cmakelists}") 353 | 354 | file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") 355 | file(WRITE "${done_location}" "DONE") 356 | 357 | hunter_gate_status_debug("Finished") 358 | endfunction() 359 | 360 | # Must be a macro so master file 'cmake/Hunter' can 361 | # apply all variables easily just by 'include' command 362 | # (otherwise PARENT_SCOPE magic needed) 363 | macro(HunterGate) 364 | if(HUNTER_GATE_DONE) 365 | # variable HUNTER_GATE_DONE set explicitly for external project 366 | # (see `hunter_download`) 367 | set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) 368 | endif() 369 | 370 | # First HunterGate command will init Hunter, others will be ignored 371 | get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) 372 | 373 | if(NOT HUNTER_ENABLED) 374 | # Empty function to avoid error "unknown function" 375 | function(hunter_add_package) 376 | endfunction() 377 | 378 | set( 379 | _hunter_gate_disabled_mode_dir 380 | "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" 381 | ) 382 | if(EXISTS "${_hunter_gate_disabled_mode_dir}") 383 | hunter_gate_status_debug( 384 | "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" 385 | ) 386 | list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") 387 | endif() 388 | elseif(_hunter_gate_done) 389 | hunter_gate_status_debug("Secondary HunterGate (use old settings)") 390 | hunter_gate_self( 391 | "${HUNTER_CACHED_ROOT}" 392 | "${HUNTER_VERSION}" 393 | "${HUNTER_SHA1}" 394 | _hunter_self 395 | ) 396 | include("${_hunter_self}/cmake/Hunter") 397 | else() 398 | set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_LIST_DIR}") 399 | 400 | string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) 401 | if(_have_project_name) 402 | hunter_gate_fatal_error( 403 | "Please set HunterGate *before* 'project' command. " 404 | "Detected project: ${PROJECT_NAME}" 405 | WIKI "error.huntergate.before.project" 406 | ) 407 | endif() 408 | 409 | cmake_parse_arguments( 410 | HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} 411 | ) 412 | 413 | string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) 414 | string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) 415 | string( 416 | COMPARE 417 | NOTEQUAL 418 | "${HUNTER_GATE_UNPARSED_ARGUMENTS}" 419 | "" 420 | _have_unparsed 421 | ) 422 | string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) 423 | string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) 424 | 425 | if(_have_unparsed) 426 | hunter_gate_user_error( 427 | "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" 428 | ) 429 | endif() 430 | if(_empty_sha1) 431 | hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") 432 | endif() 433 | if(_empty_url) 434 | hunter_gate_user_error("URL suboption of HunterGate is mandatory") 435 | endif() 436 | if(_have_global) 437 | if(HUNTER_GATE_LOCAL) 438 | hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") 439 | endif() 440 | if(_have_filepath) 441 | hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") 442 | endif() 443 | endif() 444 | if(HUNTER_GATE_LOCAL) 445 | if(_have_global) 446 | hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") 447 | endif() 448 | if(_have_filepath) 449 | hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") 450 | endif() 451 | endif() 452 | if(_have_filepath) 453 | if(_have_global) 454 | hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") 455 | endif() 456 | if(HUNTER_GATE_LOCAL) 457 | hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") 458 | endif() 459 | endif() 460 | 461 | hunter_gate_detect_root() # set HUNTER_GATE_ROOT 462 | 463 | # Beautify path, fix probable problems with windows path slashes 464 | get_filename_component( 465 | HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE 466 | ) 467 | hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") 468 | if(NOT HUNTER_ALLOW_SPACES_IN_PATH) 469 | string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) 470 | if(NOT _contain_spaces EQUAL -1) 471 | hunter_gate_fatal_error( 472 | "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." 473 | "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" 474 | "(Use at your own risk!)" 475 | WIKI "error.spaces.in.hunter.root" 476 | ) 477 | endif() 478 | endif() 479 | 480 | string( 481 | REGEX 482 | MATCH 483 | "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" 484 | HUNTER_GATE_VERSION 485 | "${HUNTER_GATE_URL}" 486 | ) 487 | string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) 488 | if(_is_empty) 489 | set(HUNTER_GATE_VERSION "unknown") 490 | endif() 491 | 492 | hunter_gate_self( 493 | "${HUNTER_GATE_ROOT}" 494 | "${HUNTER_GATE_VERSION}" 495 | "${HUNTER_GATE_SHA1}" 496 | _hunter_self 497 | ) 498 | 499 | set(_master_location "${_hunter_self}/cmake/Hunter") 500 | if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") 501 | # Hunter downloaded manually (e.g. by 'git clone') 502 | set(_unused "xxxxxxxxxx") 503 | set(HUNTER_GATE_SHA1 "${_unused}") 504 | set(HUNTER_GATE_VERSION "${_unused}") 505 | else() 506 | get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) 507 | set(_done_location "${_archive_id_location}/DONE") 508 | set(_sha1_location "${_archive_id_location}/SHA1") 509 | 510 | # Check Hunter already downloaded by HunterGate 511 | if(NOT EXISTS "${_done_location}") 512 | hunter_gate_download("${_archive_id_location}") 513 | endif() 514 | 515 | if(NOT EXISTS "${_done_location}") 516 | hunter_gate_internal_error("hunter_gate_download failed") 517 | endif() 518 | 519 | if(NOT EXISTS "${_sha1_location}") 520 | hunter_gate_internal_error("${_sha1_location} not found") 521 | endif() 522 | file(READ "${_sha1_location}" _sha1_value) 523 | string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) 524 | if(NOT _is_equal) 525 | hunter_gate_internal_error( 526 | "Short SHA1 collision:" 527 | " ${_sha1_value} (from ${_sha1_location})" 528 | " ${HUNTER_GATE_SHA1} (HunterGate)" 529 | ) 530 | endif() 531 | if(NOT EXISTS "${_master_location}") 532 | hunter_gate_user_error( 533 | "Master file not found:" 534 | " ${_master_location}" 535 | "try to update Hunter/HunterGate" 536 | ) 537 | endif() 538 | endif() 539 | include("${_master_location}") 540 | set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) 541 | endif() 542 | endmacro() 543 | -------------------------------------------------------------------------------- /cmake/ParseAndAddCatchTests.cmake: -------------------------------------------------------------------------------- 1 | #==================================================================================================# 2 | # supported macros # 3 | # - TEST_CASE, # 4 | # - SCENARIO, # 5 | # - TEST_CASE_METHOD, # 6 | # - CATCH_TEST_CASE, # 7 | # - CATCH_SCENARIO, # 8 | # - CATCH_TEST_CASE_METHOD. # 9 | # # 10 | # Usage # 11 | # 1. make sure this module is in the path or add this otherwise: # 12 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 13 | # 2. make sure that you've enabled testing option for the project by the call: # 14 | # enable_testing() # 15 | # 3. add the lines to the script for testing target (sample CMakeLists.txt): # 16 | # project(testing_target) # 17 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 18 | # enable_testing() # 19 | # # 20 | # find_path(CATCH_INCLUDE_DIR "catch.hpp") # 21 | # include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) # 22 | # # 23 | # file(GLOB SOURCE_FILES "*.cpp") # 24 | # add_executable(${PROJECT_NAME} ${SOURCE_FILES}) # 25 | # # 26 | # include(ParseAndAddCatchTests) # 27 | # ParseAndAddCatchTests(${PROJECT_NAME}) # 28 | # # 29 | # The following variables affect the behavior of the script: # 30 | # # 31 | # PARSE_CATCH_TESTS_VERBOSE (Default OFF) # 32 | # -- enables debug messages # 33 | # PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) # 34 | # -- excludes tests marked with [!hide], [.] or [.foo] tags # 35 | # PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) # 36 | # -- adds fixture class name to the test name # 37 | # PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) # 38 | # -- adds cmake target name to the test name # 39 | # PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # 40 | # -- causes CMake to rerun when file with tests changes so that new tests will be discovered # 41 | # # 42 | #==================================================================================================# 43 | 44 | cmake_minimum_required(VERSION 2.8.8) 45 | 46 | option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) 47 | option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) 48 | option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON) 49 | option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) 50 | option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) 51 | 52 | function(PrintDebugMessage) 53 | if(PARSE_CATCH_TESTS_VERBOSE) 54 | message(STATUS "ParseAndAddCatchTests: ${ARGV}") 55 | endif() 56 | endfunction() 57 | 58 | # This removes the contents between 59 | # - block comments (i.e. /* ... */) 60 | # - full line comments (i.e. // ... ) 61 | # contents have been read into '${CppCode}'. 62 | # !keep partial line comments 63 | function(RemoveComments CppCode) 64 | string(ASCII 2 CMakeBeginBlockComment) 65 | string(ASCII 3 CMakeEndBlockComment) 66 | string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") 67 | string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}") 68 | string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}") 69 | string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}") 70 | 71 | set(${CppCode} "${${CppCode}}" PARENT_SCOPE) 72 | endfunction() 73 | 74 | # Worker function 75 | function(ParseFile SourceFile TestTarget) 76 | # According to CMake docs EXISTS behavior is well-defined only for full paths. 77 | get_filename_component(SourceFile ${SourceFile} ABSOLUTE) 78 | if(NOT EXISTS ${SourceFile}) 79 | message(WARNING "Cannot find source file: ${SourceFile}") 80 | return() 81 | endif() 82 | PrintDebugMessage("parsing ${SourceFile}") 83 | file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) 84 | 85 | # Remove block and fullline comments 86 | RemoveComments(Contents) 87 | 88 | # Find definition of test names 89 | 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}") 90 | 91 | if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) 92 | PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") 93 | set_property( 94 | DIRECTORY 95 | APPEND 96 | PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} 97 | ) 98 | endif() 99 | 100 | foreach(TestName ${Tests}) 101 | # Strip newlines 102 | string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") 103 | 104 | # Get test type and fixture if applicable 105 | string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") 106 | string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") 107 | string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}") 108 | 109 | # Get string parts of test definition 110 | string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") 111 | 112 | # Strip wrapping quotation marks 113 | string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") 114 | string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") 115 | 116 | # Validate that a test name and tags have been provided 117 | list(LENGTH TestStrings TestStringsLength) 118 | if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) 119 | message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") 120 | endif() 121 | 122 | # Assign name and tags 123 | list(GET TestStrings 0 Name) 124 | if("${TestType}" STREQUAL "SCENARIO") 125 | set(Name "Scenario: ${Name}") 126 | endif() 127 | if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture) 128 | set(CTestName "${TestFixture}:${Name}") 129 | else() 130 | set(CTestName "${Name}") 131 | endif() 132 | if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) 133 | set(CTestName "${TestTarget}:${CTestName}") 134 | endif() 135 | # add target to labels to enable running all tests added from this target 136 | set(Labels ${TestTarget}) 137 | if(TestStringsLength EQUAL 2) 138 | list(GET TestStrings 1 Tags) 139 | string(TOLOWER "${Tags}" Tags) 140 | # remove target from labels if the test is hidden 141 | if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") 142 | list(REMOVE_ITEM Labels ${TestTarget}) 143 | endif() 144 | string(REPLACE "]" ";" Tags "${Tags}") 145 | string(REPLACE "[" "" Tags "${Tags}") 146 | endif() 147 | 148 | list(APPEND Labels ${Tags}) 149 | 150 | list(FIND Labels "!hide" IndexOfHideLabel) 151 | set(HiddenTagFound OFF) 152 | foreach(label ${Labels}) 153 | string(REGEX MATCH "^!hide|^\\." result ${label}) 154 | if(result) 155 | set(HiddenTagFound ON) 156 | break() 157 | endif(result) 158 | endforeach(label) 159 | if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound}) 160 | PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") 161 | else() 162 | PrintDebugMessage("Adding test \"${CTestName}\"") 163 | if(Labels) 164 | PrintDebugMessage("Setting labels to ${Labels}") 165 | endif() 166 | 167 | # Add the test and set its properties 168 | add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters}) 169 | set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" 170 | LABELS "${Labels}") 171 | endif() 172 | 173 | endforeach() 174 | endfunction() 175 | 176 | # entry point 177 | function(ParseAndAddCatchTests TestTarget) 178 | PrintDebugMessage("Started parsing ${TestTarget}") 179 | get_target_property(SourceFiles ${TestTarget} SOURCES) 180 | PrintDebugMessage("Found the following sources: ${SourceFiles}") 181 | foreach(SourceFile ${SourceFiles}) 182 | ParseFile(${SourceFile} ${TestTarget}) 183 | endforeach() 184 | PrintDebugMessage("Finished parsing ${TestTarget}") 185 | endfunction() 186 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MODERN_SQLITE_VERSION 3002008 11 | 12 | #include 13 | 14 | #include "sqlite_modern_cpp/type_wrapper.h" 15 | #include "sqlite_modern_cpp/errors.h" 16 | #include "sqlite_modern_cpp/utility/function_traits.h" 17 | #include "sqlite_modern_cpp/utility/uncaught_exceptions.h" 18 | #include "sqlite_modern_cpp/utility/utf16_utf8.h" 19 | 20 | namespace sqlite { 21 | 22 | class database; 23 | class database_binder; 24 | 25 | template class binder; 26 | 27 | typedef std::shared_ptr connection_type; 28 | 29 | template 30 | struct index_binding_helper { 31 | index_binding_helper(const index_binding_helper &) = delete; 32 | #if __cplusplus < 201703 || _MSVC_LANG <= 201703 33 | index_binding_helper(index_binding_helper &&) = default; 34 | #endif 35 | typename std::conditional::type index; 36 | T value; 37 | }; 38 | 39 | template 40 | auto named_parameter(const char *name, T &&arg) { 41 | return index_binding_helper{name, std::forward(arg)}; 42 | } 43 | template 44 | auto indexed_parameter(int index, T &&arg) { 45 | return index_binding_helper{index, std::forward(arg)}; 46 | } 47 | 48 | class row_iterator; 49 | class database_binder { 50 | 51 | public: 52 | // database_binder is not copyable 53 | database_binder() = delete; 54 | database_binder(const database_binder& other) = delete; 55 | database_binder& operator=(const database_binder&) = delete; 56 | 57 | database_binder(database_binder&& other) : 58 | _db(std::move(other._db)), 59 | _stmt(std::move(other._stmt)), 60 | _inx(other._inx), execution_started(other.execution_started) { } 61 | 62 | void execute(); 63 | 64 | std::string sql() { 65 | #if SQLITE_VERSION_NUMBER >= 3014000 66 | auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; 67 | std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); 68 | return str ? str.get() : original_sql(); 69 | #else 70 | return original_sql(); 71 | #endif 72 | } 73 | 74 | std::string original_sql() { 75 | return sqlite3_sql(_stmt.get()); 76 | } 77 | 78 | void used(bool state) { 79 | if(!state) { 80 | // We may have to reset first if we haven't done so already: 81 | _next_index(); 82 | --_inx; 83 | } 84 | execution_started = state; 85 | } 86 | bool used() const { return execution_started; } 87 | row_iterator begin(); 88 | row_iterator end(); 89 | 90 | private: 91 | std::shared_ptr _db; 92 | std::unique_ptr _stmt; 93 | utility::UncaughtExceptionDetector _has_uncaught_exception; 94 | 95 | int _inx; 96 | 97 | bool execution_started = false; 98 | 99 | int _next_index() { 100 | if(execution_started && !_inx) { 101 | sqlite3_reset(_stmt.get()); 102 | sqlite3_clear_bindings(_stmt.get()); 103 | } 104 | return ++_inx; 105 | } 106 | 107 | sqlite3_stmt* _prepare(u16str_ref sql) { 108 | return _prepare(utility::utf16_to_utf8(sql)); 109 | } 110 | 111 | sqlite3_stmt* _prepare(str_ref sql) { 112 | int hresult; 113 | sqlite3_stmt* tmp = nullptr; 114 | const char *remaining; 115 | hresult = sqlite3_prepare_v2(_db.get(), sql.data(), sql.length(), &tmp, &remaining); 116 | if(hresult != SQLITE_OK) errors::throw_sqlite_error(hresult, sql, sqlite3_errmsg(_db.get())); 117 | if(!std::all_of(remaining, sql.data() + sql.size(), [](char ch) {return std::isspace(ch);})) 118 | throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql); 119 | return tmp; 120 | } 121 | 122 | template friend database_binder& operator<<(database_binder& db, T&&); 123 | template friend database_binder& operator<<(database_binder& db, index_binding_helper); 124 | template friend database_binder& operator<<(database_binder& db, index_binding_helper); 125 | friend void operator++(database_binder& db, int); 126 | 127 | public: 128 | 129 | database_binder(std::shared_ptr db, u16str_ref sql): 130 | _db(db), 131 | _stmt(_prepare(sql), sqlite3_finalize), 132 | _inx(0) { 133 | } 134 | 135 | database_binder(std::shared_ptr db, str_ref sql): 136 | _db(db), 137 | _stmt(_prepare(sql), sqlite3_finalize), 138 | _inx(0) { 139 | } 140 | 141 | ~database_binder() noexcept(false) { 142 | /* Will be executed if no >>op is found, but not if an exception 143 | is in mid flight */ 144 | if(!used() && !_has_uncaught_exception && _stmt) { 145 | execute(); 146 | } 147 | } 148 | 149 | friend class row_iterator; 150 | }; 151 | 152 | class row_iterator { 153 | public: 154 | class value_type { 155 | public: 156 | value_type(database_binder *_binder): _binder(_binder) {}; 157 | template 158 | typename std::enable_if::value, value_type &>::type operator >>(T &result) { 159 | result = get_col_from_db(_binder->_stmt.get(), next_index++, result_type()); 160 | return *this; 161 | } 162 | template 163 | value_type &operator >>(std::tuple& values) { 164 | values = handle_tuple::type...>>(std::index_sequence_for()); 165 | next_index += sizeof...(Types); 166 | return *this; 167 | } 168 | template 169 | value_type &operator >>(std::tuple&& values) { 170 | return *this >> values; 171 | } 172 | template 173 | operator std::tuple() { 174 | std::tuple value; 175 | *this >> value; 176 | return value; 177 | } 178 | explicit operator bool() { 179 | return sqlite3_column_count(_binder->_stmt.get()) >= next_index; 180 | } 181 | private: 182 | template 183 | Tuple handle_tuple(std::index_sequence) { 184 | return Tuple( 185 | get_col_from_db( 186 | _binder->_stmt.get(), 187 | next_index + Index, 188 | result_type::type>())...); 189 | } 190 | database_binder *_binder; 191 | int next_index = 0; 192 | }; 193 | using difference_type = std::ptrdiff_t; 194 | using pointer = value_type*; 195 | using reference = value_type&; 196 | using iterator_category = std::input_iterator_tag; 197 | 198 | row_iterator() = default; 199 | explicit row_iterator(database_binder &binder): _binder(&binder) { 200 | _binder->_next_index(); 201 | _binder->_inx = 0; 202 | _binder->used(true); 203 | ++*this; 204 | } 205 | 206 | reference operator*() const { return value;} 207 | pointer operator->() const { return std::addressof(**this); } 208 | row_iterator &operator++() { 209 | switch(int result = sqlite3_step(_binder->_stmt.get())) { 210 | case SQLITE_ROW: 211 | value = {_binder}; 212 | break; 213 | case SQLITE_DONE: 214 | _binder = nullptr; 215 | break; 216 | default: 217 | exceptions::throw_sqlite_error(result, _binder->sql(), sqlite3_errmsg(_binder->_db.get())); 218 | } 219 | return *this; 220 | } 221 | 222 | friend inline bool operator ==(const row_iterator &a, const row_iterator &b) { 223 | return a._binder == b._binder; 224 | } 225 | friend inline bool operator !=(const row_iterator &a, const row_iterator &b) { 226 | return !(a==b); 227 | } 228 | 229 | private: 230 | database_binder *_binder = nullptr; 231 | mutable value_type value{_binder}; // mutable, because `changing` the value is just reading it 232 | }; 233 | 234 | inline row_iterator database_binder::begin() { 235 | return row_iterator(*this); 236 | } 237 | 238 | inline row_iterator database_binder::end() { 239 | return row_iterator(); 240 | } 241 | 242 | namespace detail { 243 | template 244 | void _extract_single_value(database_binder &binder, Callback call_back) { 245 | auto iter = binder.begin(); 246 | if(iter == binder.end()) 247 | throw errors::no_rows("no rows to extract: exactly 1 row expected", binder.sql(), SQLITE_DONE); 248 | 249 | call_back(*iter); 250 | 251 | if(++iter != binder.end()) 252 | throw errors::more_rows("not all rows extracted", binder.sql(), SQLITE_ROW); 253 | } 254 | } 255 | inline void database_binder::execute() { 256 | for(auto &&row : *this) 257 | (void)row; 258 | } 259 | namespace detail { 260 | template using void_t = void; 261 | template 262 | struct sqlite_direct_result : std::false_type {}; 263 | template 264 | struct sqlite_direct_result< 265 | T, 266 | void_t() >> std::declval())> 267 | > : std::true_type {}; 268 | } 269 | template 270 | inline typename std::enable_if::value>::type operator>>(database_binder &binder, Result&& value) { 271 | detail::_extract_single_value(binder, [&value] (row_iterator::value_type &row) { 272 | row >> std::forward(value); 273 | }); 274 | } 275 | 276 | template 277 | inline typename std::enable_if::value>::type operator>>(database_binder &db_binder, Function&& func) { 278 | using traits = utility::function_traits; 279 | 280 | for(auto &&row : db_binder) { 281 | binder::run(row, func); 282 | } 283 | } 284 | 285 | template 286 | inline decltype(auto) operator>>(database_binder &&binder, Result&& value) { 287 | return binder >> std::forward(value); 288 | } 289 | 290 | namespace sql_function_binder { 291 | template< 292 | typename ContextType, 293 | std::size_t Count, 294 | typename Functions 295 | > 296 | inline void step( 297 | sqlite3_context* db, 298 | int count, 299 | sqlite3_value** vals 300 | ); 301 | 302 | template< 303 | std::size_t Count, 304 | typename Functions, 305 | typename... Values 306 | > 307 | inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step( 308 | sqlite3_context* db, 309 | int count, 310 | sqlite3_value** vals, 311 | Values&&... values 312 | ); 313 | 314 | template< 315 | std::size_t Count, 316 | typename Functions, 317 | typename... Values 318 | > 319 | inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step( 320 | sqlite3_context* db, 321 | int, 322 | sqlite3_value**, 323 | Values&&... values 324 | ); 325 | 326 | template< 327 | typename ContextType, 328 | typename Functions 329 | > 330 | inline void final(sqlite3_context* db); 331 | 332 | template< 333 | std::size_t Count, 334 | typename Function, 335 | typename... Values 336 | > 337 | inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar( 338 | sqlite3_context* db, 339 | int count, 340 | sqlite3_value** vals, 341 | Values&&... values 342 | ); 343 | 344 | template< 345 | std::size_t Count, 346 | typename Function, 347 | typename... Values 348 | > 349 | inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar( 350 | sqlite3_context* db, 351 | int, 352 | sqlite3_value**, 353 | Values&&... values 354 | ); 355 | } 356 | 357 | enum class OpenFlags { 358 | READONLY = SQLITE_OPEN_READONLY, 359 | READWRITE = SQLITE_OPEN_READWRITE, 360 | CREATE = SQLITE_OPEN_CREATE, 361 | NOMUTEX = SQLITE_OPEN_NOMUTEX, 362 | FULLMUTEX = SQLITE_OPEN_FULLMUTEX, 363 | SHAREDCACHE = SQLITE_OPEN_SHAREDCACHE, 364 | PRIVATECACH = SQLITE_OPEN_PRIVATECACHE, 365 | URI = SQLITE_OPEN_URI 366 | }; 367 | inline OpenFlags operator|(const OpenFlags& a, const OpenFlags& b) { 368 | return static_cast(static_cast(a) | static_cast(b)); 369 | } 370 | enum class Encoding { 371 | ANY = SQLITE_ANY, 372 | UTF8 = SQLITE_UTF8, 373 | UTF16 = SQLITE_UTF16 374 | }; 375 | struct sqlite_config { 376 | OpenFlags flags = OpenFlags::READWRITE | OpenFlags::CREATE; 377 | const char *zVfs = nullptr; 378 | Encoding encoding = Encoding::ANY; 379 | }; 380 | 381 | class database { 382 | protected: 383 | std::shared_ptr _db; 384 | 385 | public: 386 | database(const std::string &db_name, const sqlite_config &config = {}): _db(nullptr) { 387 | sqlite3* tmp = nullptr; 388 | auto ret = sqlite3_open_v2(db_name.data(), &tmp, static_cast(config.flags), config.zVfs); 389 | _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. 390 | if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret, {}, sqlite3_errmsg(_db.get())); 391 | sqlite3_extended_result_codes(_db.get(), true); 392 | if(config.encoding == Encoding::UTF16) 393 | *this << R"(PRAGMA encoding = "UTF-16";)"; 394 | } 395 | 396 | database(const std::u16string &db_name, const sqlite_config &config = {}): database(utility::utf16_to_utf8(db_name), config) { 397 | if (config.encoding == Encoding::ANY) 398 | *this << R"(PRAGMA encoding = "UTF-16";)"; 399 | } 400 | 401 | database(std::shared_ptr db): 402 | _db(db) {} 403 | 404 | database_binder operator<<(str_ref sql) { 405 | return database_binder(_db, sql); 406 | } 407 | 408 | database_binder operator<<(u16str_ref sql) { 409 | return database_binder(_db, sql); 410 | } 411 | 412 | connection_type connection() const { return _db; } 413 | 414 | sqlite3_int64 last_insert_rowid() const { 415 | return sqlite3_last_insert_rowid(_db.get()); 416 | } 417 | 418 | int rows_modified() const { 419 | return sqlite3_changes(_db.get()); 420 | } 421 | 422 | template 423 | void define(const std::string &name, Function&& func) { 424 | typedef utility::function_traits traits; 425 | 426 | auto funcPtr = new auto(std::forward(func)); 427 | if(int result = sqlite3_create_function_v2( 428 | _db.get(), name.data(), traits::arity, SQLITE_UTF8, funcPtr, 429 | sql_function_binder::scalar::type>, 430 | nullptr, nullptr, [](void* ptr){ 431 | delete static_cast(ptr); 432 | })) 433 | errors::throw_sqlite_error(result, {}, sqlite3_errmsg(_db.get())); 434 | } 435 | 436 | template 437 | void define(const std::string &name, StepFunction&& step, FinalFunction&& final) { 438 | typedef utility::function_traits traits; 439 | using ContextType = typename std::remove_reference>::type; 440 | 441 | auto funcPtr = new auto(std::make_pair(std::forward(step), std::forward(final))); 442 | if(int result = sqlite3_create_function_v2( 443 | _db.get(), name.c_str(), traits::arity - 1, SQLITE_UTF8, funcPtr, nullptr, 444 | sql_function_binder::step::type>, 445 | sql_function_binder::final::type>, 446 | [](void* ptr){ 447 | delete static_cast(ptr); 448 | })) 449 | errors::throw_sqlite_error(result, {}, sqlite3_errmsg(_db.get())); 450 | } 451 | 452 | }; 453 | 454 | template 455 | class binder { 456 | private: 457 | template < 458 | typename Function, 459 | std::size_t Index 460 | > 461 | using nth_argument_type = typename utility::function_traits< 462 | Function 463 | >::template argument; 464 | 465 | public: 466 | // `Boundary` needs to be defaulted to `Count` so that the `run` function 467 | // template is not implicitly instantiated on class template instantiation. 468 | // Look up section 14.7.1 _Implicit instantiation_ of the ISO C++14 Standard 469 | // and the [dicussion](https://github.com/aminroosta/sqlite_modern_cpp/issues/8) 470 | // on Github. 471 | 472 | template< 473 | typename Function, 474 | typename... Values, 475 | std::size_t Boundary = Count 476 | > 477 | static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run( 478 | row_iterator::value_type& row, 479 | Function&& function, 480 | Values&&... values 481 | ) { 482 | typename std::decay>::type value; 483 | row >> value; 484 | run(row, function, std::forward(values)..., std::move(value)); 485 | } 486 | 487 | template< 488 | typename Function, 489 | typename... Values, 490 | std::size_t Boundary = Count 491 | > 492 | static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run( 493 | row_iterator::value_type&, 494 | Function&& function, 495 | Values&&... values 496 | ) { 497 | function(std::move(values)...); 498 | } 499 | }; 500 | 501 | // Some ppl are lazy so we have a operator for proper prep. statemant handling. 502 | void inline operator++(database_binder& db, int) { db.execute(); } 503 | 504 | template database_binder &operator<<(database_binder& db, index_binding_helper val) { 505 | db._next_index(); --db._inx; 506 | int result = bind_col_in_db(db._stmt.get(), val.index, std::forward(val.value)); 507 | if(result != SQLITE_OK) 508 | exceptions::throw_sqlite_error(result, db.sql(), sqlite3_errmsg(db._db.get())); 509 | return db; 510 | } 511 | 512 | template database_binder &operator<<(database_binder& db, index_binding_helper val) { 513 | db._next_index(); --db._inx; 514 | int index = sqlite3_bind_parameter_index(db._stmt.get(), val.index); 515 | if(!index) 516 | throw errors::unknown_binding("The given binding name is not valid for this statement", db.sql()); 517 | int result = bind_col_in_db(db._stmt.get(), index, std::forward(val.value)); 518 | if(result != SQLITE_OK) 519 | exceptions::throw_sqlite_error(result, db.sql(), sqlite3_errmsg(db._db.get())); 520 | return db; 521 | } 522 | 523 | template database_binder &operator<<(database_binder& db, T&& val) { 524 | int result = bind_col_in_db(db._stmt.get(), db._next_index(), std::forward(val)); 525 | if(result != SQLITE_OK) 526 | exceptions::throw_sqlite_error(result, db.sql(), sqlite3_errmsg(db._db.get())); 527 | return db; 528 | } 529 | // 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!) 530 | template database_binder operator << (database_binder&& db, const T& val) { db << val; return std::move(db); } 531 | template database_binder operator << (database_binder&& db, index_binding_helper val) { db << index_binding_helper{val.index, std::forward(val.value)}; return std::move(db); } 532 | 533 | namespace sql_function_binder { 534 | template 535 | struct AggregateCtxt { 536 | T obj; 537 | bool constructed = true; 538 | }; 539 | 540 | template< 541 | typename ContextType, 542 | std::size_t Count, 543 | typename Functions 544 | > 545 | inline void step( 546 | sqlite3_context* db, 547 | int count, 548 | sqlite3_value** vals 549 | ) { 550 | auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); 551 | if(!ctxt) return; 552 | try { 553 | if(!ctxt->constructed) new(ctxt) AggregateCtxt(); 554 | step(db, count, vals, ctxt->obj); 555 | return; 556 | } catch(const sqlite_exception &e) { 557 | sqlite3_result_error_code(db, e.get_code()); 558 | sqlite3_result_error(db, e.what(), -1); 559 | } catch(const std::exception &e) { 560 | sqlite3_result_error(db, e.what(), -1); 561 | } catch(...) { 562 | sqlite3_result_error(db, "Unknown error", -1); 563 | } 564 | if(ctxt && ctxt->constructed) 565 | ctxt->~AggregateCtxt(); 566 | } 567 | 568 | template< 569 | std::size_t Count, 570 | typename Functions, 571 | typename... Values 572 | > 573 | inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step( 574 | sqlite3_context* db, 575 | int count, 576 | sqlite3_value** vals, 577 | Values&&... values 578 | ) { 579 | using arg_type = typename std::remove_cv< 580 | typename std::remove_reference< 581 | typename utility::function_traits< 582 | typename Functions::first_type 583 | >::template argument 584 | >::type 585 | >::type; 586 | 587 | step( 588 | db, 589 | count, 590 | vals, 591 | std::forward(values)..., 592 | get_val_from_db(vals[sizeof...(Values) - 1], result_type())); 593 | } 594 | 595 | template< 596 | std::size_t Count, 597 | typename Functions, 598 | typename... Values 599 | > 600 | inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step( 601 | sqlite3_context* db, 602 | int, 603 | sqlite3_value**, 604 | Values&&... values 605 | ) { 606 | static_cast(sqlite3_user_data(db))->first(std::forward(values)...); 607 | } 608 | 609 | template< 610 | typename ContextType, 611 | typename Functions 612 | > 613 | inline void final(sqlite3_context* db) { 614 | auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); 615 | try { 616 | if(!ctxt) return; 617 | if(!ctxt->constructed) new(ctxt) AggregateCtxt(); 618 | store_result_in_db(db, 619 | static_cast(sqlite3_user_data(db))->second(ctxt->obj)); 620 | } catch(const sqlite_exception &e) { 621 | sqlite3_result_error_code(db, e.get_code()); 622 | sqlite3_result_error(db, e.what(), -1); 623 | } catch(const std::exception &e) { 624 | sqlite3_result_error(db, e.what(), -1); 625 | } catch(...) { 626 | sqlite3_result_error(db, "Unknown error", -1); 627 | } 628 | if(ctxt && ctxt->constructed) 629 | ctxt->~AggregateCtxt(); 630 | } 631 | 632 | template< 633 | std::size_t Count, 634 | typename Function, 635 | typename... Values 636 | > 637 | inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar( 638 | sqlite3_context* db, 639 | int count, 640 | sqlite3_value** vals, 641 | Values&&... values 642 | ) { 643 | using arg_type = typename std::remove_cv< 644 | typename std::remove_reference< 645 | typename utility::function_traits::template argument 646 | >::type 647 | >::type; 648 | 649 | scalar( 650 | db, 651 | count, 652 | vals, 653 | std::forward(values)..., 654 | get_val_from_db(vals[sizeof...(Values)], result_type())); 655 | } 656 | 657 | template< 658 | std::size_t Count, 659 | typename Function, 660 | typename... Values 661 | > 662 | inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar( 663 | sqlite3_context* db, 664 | int, 665 | sqlite3_value**, 666 | Values&&... values 667 | ) { 668 | try { 669 | store_result_in_db(db, 670 | (*static_cast(sqlite3_user_data(db)))(std::forward(values)...)); 671 | } catch(const sqlite_exception &e) { 672 | sqlite3_result_error_code(db, e.get_code()); 673 | sqlite3_result_error(db, e.what(), -1); 674 | } catch(const std::exception &e) { 675 | sqlite3_result_error(db, e.what(), -1); 676 | } catch(...) { 677 | sqlite3_result_error(db, "Unknown error", -1); 678 | } 679 | } 680 | } 681 | } 682 | 683 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/errors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace sqlite { 9 | 10 | class sqlite_exception: public std::runtime_error { 11 | public: 12 | sqlite_exception(const char* msg, str_ref sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} 13 | sqlite_exception(int code, str_ref sql, const char *msg = nullptr): runtime_error(msg ? msg : sqlite3_errstr(code)), code(code), sql(sql) {} 14 | int get_code() const {return code & 0xFF;} 15 | int get_extended_code() const {return code;} 16 | std::string get_sql() const {return sql;} 17 | const char *errstr() const {return code == -1 ? "Unknown error" : sqlite3_errstr(code);} 18 | private: 19 | int code; 20 | std::string sql; 21 | }; 22 | 23 | namespace errors { 24 | //One more or less trivial derived error class for each SQLITE error. 25 | //Note the following are not errors so have no classes: 26 | //SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE 27 | // 28 | //Note these names are exact matches to the names of the SQLITE error codes. 29 | #define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ 30 | class name: public sqlite_exception { using sqlite_exception::sqlite_exception; };\ 31 | derived 32 | #define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ 33 | class base ## _ ## sub: public base { using base::base; }; 34 | #include "lists/error_codes.h" 35 | #undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED 36 | #undef SQLITE_MODERN_CPP_ERROR_CODE 37 | 38 | //Some additional errors are here for the C++ interface 39 | class more_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; 40 | class no_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; 41 | class more_statements: public sqlite_exception { using sqlite_exception::sqlite_exception; }; // Prepared statements can only contain one statement 42 | class invalid_utf16: public sqlite_exception { using sqlite_exception::sqlite_exception; }; 43 | class unknown_binding: public sqlite_exception { using sqlite_exception::sqlite_exception; }; 44 | 45 | static void throw_sqlite_error(const int& error_code, str_ref sql = "", const char *errmsg = nullptr) { 46 | switch(error_code & 0xFF) { 47 | #define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ 48 | case SQLITE_ ## NAME: switch(error_code) { \ 49 | derived \ 50 | case SQLITE_ ## NAME: \ 51 | default: throw name(error_code, sql); \ 52 | } 53 | 54 | #if SQLITE_VERSION_NUMBER < 3010000 55 | #define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) 56 | #define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) 57 | #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) 58 | #endif 59 | 60 | #define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ 61 | case SQLITE_ ## BASE ## _ ## SUB: throw base ## _ ## sub(error_code, sql, errmsg); 62 | #include "lists/error_codes.h" 63 | #undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED 64 | #undef SQLITE_MODERN_CPP_ERROR_CODE 65 | default: throw sqlite_exception(error_code, sql, errmsg); 66 | } 67 | } 68 | } 69 | namespace exceptions = errors; 70 | } 71 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/lists/error_codes.h: -------------------------------------------------------------------------------- 1 | SQLITE_MODERN_CPP_ERROR_CODE(ERROR,error,) 2 | SQLITE_MODERN_CPP_ERROR_CODE(INTERNAL,internal,) 3 | SQLITE_MODERN_CPP_ERROR_CODE(PERM,perm,) 4 | SQLITE_MODERN_CPP_ERROR_CODE(ABORT,abort, 5 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(ABORT,ROLLBACK,abort,rollback) 6 | ) 7 | SQLITE_MODERN_CPP_ERROR_CODE(BUSY,busy, 8 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BUSY,RECOVERY,busy,recovery) 9 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BUSY,SNAPSHOT,busy,snapshot) 10 | ) 11 | SQLITE_MODERN_CPP_ERROR_CODE(LOCKED,locked, 12 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(LOCKED,SHAREDCACHE,locked,sharedcache) 13 | ) 14 | SQLITE_MODERN_CPP_ERROR_CODE(NOMEM,nomem,) 15 | SQLITE_MODERN_CPP_ERROR_CODE(READONLY,readonly,) 16 | SQLITE_MODERN_CPP_ERROR_CODE(INTERRUPT,interrupt,) 17 | SQLITE_MODERN_CPP_ERROR_CODE(IOERR,ioerr, 18 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,READ,ioerr,read) 19 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHORT_READ,ioerr,short_read) 20 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,WRITE,ioerr,write) 21 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,FSYNC,ioerr,fsync) 22 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DIR_FSYNC,ioerr,dir_fsync) 23 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,TRUNCATE,ioerr,truncate) 24 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,FSTAT,ioerr,fstat) 25 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,UNLOCK,ioerr,unlock) 26 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,RDLOCK,ioerr,rdlock) 27 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DELETE,ioerr,delete) 28 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,BLOCKED,ioerr,blocked) 29 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,NOMEM,ioerr,nomem) 30 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,ACCESS,ioerr,access) 31 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CHECKRESERVEDLOCK,ioerr,checkreservedlock) 32 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,LOCK,ioerr,lock) 33 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CLOSE,ioerr,close) 34 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DIR_CLOSE,ioerr,dir_close) 35 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMOPEN,ioerr,shmopen) 36 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMSIZE,ioerr,shmsize) 37 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMLOCK,ioerr,shmlock) 38 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SHMMAP,ioerr,shmmap) 39 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,SEEK,ioerr,seek) 40 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,DELETE_NOENT,ioerr,delete_noent) 41 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,MMAP,ioerr,mmap) 42 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,GETTEMPPATH,ioerr,gettemppath) 43 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,CONVPATH,ioerr,convpath) 44 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,VNODE,ioerr,vnode) 45 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(IOERR,AUTH,ioerr,auth) 46 | ) 47 | SQLITE_MODERN_CPP_ERROR_CODE(CORRUPT,corrupt, 48 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CORRUPT,VTAB,corrupt,vtab) 49 | ) 50 | SQLITE_MODERN_CPP_ERROR_CODE(NOTFOUND,notfound,) 51 | SQLITE_MODERN_CPP_ERROR_CODE(FULL,full,) 52 | SQLITE_MODERN_CPP_ERROR_CODE(CANTOPEN,cantopen, 53 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,NOTEMPDIR,cantopen,notempdir) 54 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,ISDIR,cantopen,isdir) 55 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,FULLPATH,cantopen,fullpath) 56 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CANTOPEN,CONVPATH,cantopen,convpath) 57 | ) 58 | SQLITE_MODERN_CPP_ERROR_CODE(PROTOCOL,protocol,) 59 | SQLITE_MODERN_CPP_ERROR_CODE(EMPTY,empty,) 60 | SQLITE_MODERN_CPP_ERROR_CODE(SCHEMA,schema,) 61 | SQLITE_MODERN_CPP_ERROR_CODE(TOOBIG,toobig,) 62 | SQLITE_MODERN_CPP_ERROR_CODE(CONSTRAINT,constraint, 63 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,CHECK,constraint,check) 64 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,COMMITHOOK,constraint,commithook) 65 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,FOREIGNKEY,constraint,foreignkey) 66 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,FUNCTION,constraint,function) 67 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,NOTNULL,constraint,notnull) 68 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,PRIMARYKEY,constraint,primarykey) 69 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,TRIGGER,constraint,trigger) 70 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,UNIQUE,constraint,unique) 71 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,VTAB,constraint,vtab) 72 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(CONSTRAINT,ROWID,constraint,rowid) 73 | ) 74 | SQLITE_MODERN_CPP_ERROR_CODE(MISMATCH,mismatch,) 75 | SQLITE_MODERN_CPP_ERROR_CODE(MISUSE,misuse,) 76 | SQLITE_MODERN_CPP_ERROR_CODE(NOLFS,nolfs,) 77 | SQLITE_MODERN_CPP_ERROR_CODE(AUTH,auth, 78 | ) 79 | SQLITE_MODERN_CPP_ERROR_CODE(FORMAT,format,) 80 | SQLITE_MODERN_CPP_ERROR_CODE(RANGE,range,) 81 | SQLITE_MODERN_CPP_ERROR_CODE(NOTADB,notadb,) 82 | SQLITE_MODERN_CPP_ERROR_CODE(NOTICE,notice, 83 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(NOTICE,RECOVER_WAL,notice,recover_wal) 84 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(NOTICE,RECOVER_ROLLBACK,notice,recover_rollback) 85 | ) 86 | SQLITE_MODERN_CPP_ERROR_CODE(WARNING,warning, 87 | SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(WARNING,AUTOINDEX,warning,autoindex) 88 | ) 89 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/log.h: -------------------------------------------------------------------------------- 1 | #include "errors.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sqlite { 10 | namespace detail { 11 | template 12 | using void_t = void; 13 | template 14 | struct is_callable : std::false_type {}; 15 | template 16 | struct is_callable()(std::declval()...))>> : std::true_type {}; 17 | template 18 | class FunctorOverload: public Functor, public FunctorOverload { 19 | public: 20 | template 21 | FunctorOverload(Functor1 &&functor, Remaining &&... remaining): 22 | Functor(std::forward(functor)), 23 | FunctorOverload(std::forward(remaining)...) {} 24 | using Functor::operator(); 25 | using FunctorOverload::operator(); 26 | }; 27 | template 28 | class FunctorOverload: public Functor { 29 | public: 30 | template 31 | FunctorOverload(Functor1 &&functor): 32 | Functor(std::forward(functor)) {} 33 | using Functor::operator(); 34 | }; 35 | template 36 | class WrapIntoFunctor: public Functor { 37 | public: 38 | template 39 | WrapIntoFunctor(Functor1 &&functor): 40 | Functor(std::forward(functor)) {} 41 | using Functor::operator(); 42 | }; 43 | template 44 | class WrapIntoFunctor { 45 | ReturnType(*ptr)(Arguments...); 46 | public: 47 | WrapIntoFunctor(ReturnType(*ptr)(Arguments...)): ptr(ptr) {} 48 | ReturnType operator()(Arguments... arguments) { return (*ptr)(std::forward(arguments)...); } 49 | }; 50 | inline void store_error_log_data_pointer(std::shared_ptr ptr) { 51 | static std::shared_ptr stored; 52 | stored = std::move(ptr); 53 | } 54 | template 55 | std::shared_ptr::type> make_shared_inferred(T &&t) { 56 | return std::make_shared::type>(std::forward(t)); 57 | } 58 | } 59 | template 60 | typename std::enable_if::value>::type 61 | error_log(Handler &&handler); 62 | template 63 | typename std::enable_if::value>::type 64 | error_log(Handler &&handler); 65 | template 66 | typename std::enable_if=2>::type 67 | error_log(Handler &&...handler) { 68 | return error_log(detail::FunctorOverload::type>...>(std::forward(handler)...)); 69 | } 70 | template 71 | typename std::enable_if::value>::type 72 | error_log(Handler &&handler) { 73 | return error_log(std::forward(handler), [](const sqlite_exception&) {}); 74 | } 75 | template 76 | typename std::enable_if::value>::type 77 | error_log(Handler &&handler) { 78 | auto ptr = detail::make_shared_inferred([handler = std::forward(handler)](int error_code, const char *errstr) mutable { 79 | switch(error_code & 0xFF) { 80 | #define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ 81 | case SQLITE_ ## NAME: switch(error_code) { \ 82 | derived \ 83 | default: handler(errors::name(errstr, "", error_code)); \ 84 | };break; 85 | #define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ 86 | case SQLITE_ ## BASE ## _ ## SUB: \ 87 | handler(errors::base ## _ ## sub(errstr, "", error_code)); \ 88 | break; 89 | #include "lists/error_codes.h" 90 | #undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED 91 | #undef SQLITE_MODERN_CPP_ERROR_CODE 92 | default: handler(sqlite_exception(errstr, "", error_code)); \ 93 | } 94 | }); 95 | 96 | sqlite3_config(SQLITE_CONFIG_LOG, static_cast([](void *functor, int error_code, const char *errstr) { 97 | (*static_cast(functor))(error_code, errstr); 98 | }), ptr.get()); 99 | detail::store_error_log_data_pointer(std::move(ptr)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/sqlcipher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SQLITE_HAS_CODEC 4 | #define SQLITE_HAS_CODEC 5 | #endif 6 | 7 | #include "../sqlite_modern_cpp.h" 8 | 9 | namespace sqlite { 10 | struct sqlcipher_config : public sqlite_config { 11 | std::string key; 12 | }; 13 | 14 | class sqlcipher_database : public database { 15 | public: 16 | sqlcipher_database(std::string db, const sqlcipher_config &config): database(db, config) { 17 | set_key(config.key); 18 | } 19 | 20 | sqlcipher_database(std::u16string db, const sqlcipher_config &config): database(db, config) { 21 | set_key(config.key); 22 | } 23 | 24 | void set_key(const std::string &key) { 25 | if(auto ret = sqlite3_key(_db.get(), key.data(), key.size())) 26 | errors::throw_sqlite_error(ret); 27 | } 28 | 29 | void set_key(const std::string &key, const std::string &db_name) { 30 | if(auto ret = sqlite3_key_v2(_db.get(), db_name.c_str(), key.data(), key.size())) 31 | errors::throw_sqlite_error(ret); 32 | } 33 | 34 | void rekey(const std::string &new_key) { 35 | if(auto ret = sqlite3_rekey(_db.get(), new_key.data(), new_key.size())) 36 | errors::throw_sqlite_error(ret); 37 | } 38 | 39 | void rekey(const std::string &new_key, const std::string &db_name) { 40 | if(auto ret = sqlite3_rekey_v2(_db.get(), db_name.c_str(), new_key.data(), new_key.size())) 41 | errors::throw_sqlite_error(ret); 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/type_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef __has_include 8 | #if (__cplusplus >= 201703 || _MSVC_LANG >= 201703) && __has_include() 9 | #define MODERN_SQLITE_STRINGVIEW_SUPPORT 10 | #endif 11 | #endif 12 | #ifdef __has_include 13 | #if (__cplusplus > 201402 || _MSVC_LANG > 201402) && __has_include() 14 | #define MODERN_SQLITE_STD_OPTIONAL_SUPPORT 15 | #elif __has_include() && __apple_build_version__ < 11000000 16 | #define MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT 17 | #endif 18 | #endif 19 | 20 | #ifdef __has_include 21 | #if (__cplusplus > 201402 || _MSVC_LANG > 201402) && __has_include() 22 | #define MODERN_SQLITE_STD_VARIANT_SUPPORT 23 | #endif 24 | #endif 25 | 26 | #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT 27 | #include 28 | #endif 29 | 30 | #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT 31 | #include 32 | #define MODERN_SQLITE_STD_OPTIONAL_SUPPORT 33 | #endif 34 | 35 | #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT 36 | #include 37 | #endif 38 | #ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT 39 | #include 40 | namespace sqlite 41 | { 42 | typedef const std::string_view str_ref; 43 | typedef const std::u16string_view u16str_ref; 44 | } 45 | #else 46 | namespace sqlite 47 | { 48 | typedef const std::string& str_ref; 49 | typedef const std::u16string& u16str_ref; 50 | } 51 | #endif 52 | #include 53 | #include "errors.h" 54 | 55 | namespace sqlite { 56 | template 57 | struct has_sqlite_type : std::false_type {}; 58 | 59 | template 60 | using is_sqlite_value = std::integral_constant::value 62 | || has_sqlite_type::value 63 | || has_sqlite_type::value 64 | || has_sqlite_type::value 65 | || has_sqlite_type::value 66 | >; 67 | 68 | template 69 | struct has_sqlite_type : has_sqlite_type {}; 70 | template 71 | struct has_sqlite_type : has_sqlite_type {}; 72 | template 73 | struct has_sqlite_type : has_sqlite_type {}; 74 | 75 | template 76 | struct result_type { 77 | using type = T; 78 | constexpr result_type() = default; 79 | template::value>> 80 | constexpr result_type(result_type) { } 81 | }; 82 | 83 | // int 84 | template<> 85 | struct has_sqlite_type : std::true_type {}; 86 | 87 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const int& val) { 88 | return sqlite3_bind_int(stmt, inx, val); 89 | } 90 | inline void store_result_in_db(sqlite3_context* db, const int& val) { 91 | sqlite3_result_int(db, val); 92 | } 93 | inline int get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 94 | return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? 0 : 95 | sqlite3_column_int(stmt, inx); 96 | } 97 | inline int get_val_from_db(sqlite3_value *value, result_type) { 98 | return sqlite3_value_type(value) == SQLITE_NULL ? 0 : 99 | sqlite3_value_int(value); 100 | } 101 | 102 | // sqlite_int64 103 | template<> 104 | struct has_sqlite_type : std::true_type {}; 105 | 106 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const sqlite_int64& val) { 107 | return sqlite3_bind_int64(stmt, inx, val); 108 | } 109 | inline void store_result_in_db(sqlite3_context* db, const sqlite_int64& val) { 110 | sqlite3_result_int64(db, val); 111 | } 112 | inline sqlite_int64 get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 113 | return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? 0 : 114 | sqlite3_column_int64(stmt, inx); 115 | } 116 | inline sqlite3_int64 get_val_from_db(sqlite3_value *value, result_type) { 117 | return sqlite3_value_type(value) == SQLITE_NULL ? 0 : 118 | sqlite3_value_int64(value); 119 | } 120 | 121 | // float 122 | template<> 123 | struct has_sqlite_type : std::true_type {}; 124 | 125 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const float& val) { 126 | return sqlite3_bind_double(stmt, inx, double(val)); 127 | } 128 | inline void store_result_in_db(sqlite3_context* db, const float& val) { 129 | sqlite3_result_double(db, val); 130 | } 131 | inline float get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 132 | return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? 0 : 133 | sqlite3_column_double(stmt, inx); 134 | } 135 | inline float get_val_from_db(sqlite3_value *value, result_type) { 136 | return sqlite3_value_type(value) == SQLITE_NULL ? 0 : 137 | sqlite3_value_double(value); 138 | } 139 | 140 | // double 141 | template<> 142 | struct has_sqlite_type : std::true_type {}; 143 | 144 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const double& val) { 145 | return sqlite3_bind_double(stmt, inx, val); 146 | } 147 | inline void store_result_in_db(sqlite3_context* db, const double& val) { 148 | sqlite3_result_double(db, val); 149 | } 150 | inline double get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 151 | return sqlite3_column_type(stmt, inx) == SQLITE_NULL ? 0 : 152 | sqlite3_column_double(stmt, inx); 153 | } 154 | inline double get_val_from_db(sqlite3_value *value, result_type) { 155 | return sqlite3_value_type(value) == SQLITE_NULL ? 0 : 156 | sqlite3_value_double(value); 157 | } 158 | 159 | /* for nullptr support */ 160 | template<> 161 | struct has_sqlite_type : std::true_type {}; 162 | 163 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, std::nullptr_t) { 164 | return sqlite3_bind_null(stmt, inx); 165 | } 166 | inline void store_result_in_db(sqlite3_context* db, std::nullptr_t) { 167 | sqlite3_result_null(db); 168 | } 169 | 170 | #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT 171 | template<> 172 | struct has_sqlite_type : std::true_type {}; 173 | 174 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, std::monostate) { 175 | return sqlite3_bind_null(stmt, inx); 176 | } 177 | inline void store_result_in_db(sqlite3_context* db, std::monostate) { 178 | sqlite3_result_null(db); 179 | } 180 | inline std::monostate get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 181 | return std::monostate(); 182 | } 183 | inline std::monostate get_val_from_db(sqlite3_value *value, result_type) { 184 | return std::monostate(); 185 | } 186 | #endif 187 | 188 | // str_ref 189 | template<> 190 | struct has_sqlite_type : std::true_type {}; 191 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, str_ref val) { 192 | return sqlite3_bind_text(stmt, inx, val.data(), val.length(), SQLITE_TRANSIENT); 193 | } 194 | 195 | // Convert char* to string_view to trigger op<<(..., const str_ref ) 196 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char(&STR)[N]) { 197 | return sqlite3_bind_text(stmt, inx, &STR[0], N-1, SQLITE_TRANSIENT); 198 | } 199 | 200 | inline std::string get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 201 | if ( sqlite3_column_type(stmt, inx) == SQLITE_NULL ) { 202 | return std::string(); 203 | } 204 | char const * ptr = reinterpret_cast(sqlite3_column_text(stmt, inx)); 205 | // call sqlite3_column_text explicitely before sqlite3_column_bytes: it may convert the value to text 206 | return std::string(ptr, sqlite3_column_bytes(stmt, inx)); 207 | } 208 | inline std::string get_val_from_db(sqlite3_value *value, result_type) { 209 | if ( sqlite3_value_type(value) == SQLITE_NULL ) { 210 | return std::string(); 211 | } 212 | char const * ptr = reinterpret_cast(sqlite3_value_text(value)); 213 | // call sqlite3_column_text explicitely before sqlite3_column_bytes: it may convert the value to text 214 | return std::string(ptr, sqlite3_value_bytes(value)); 215 | } 216 | 217 | inline void store_result_in_db(sqlite3_context* db, str_ref val) { 218 | sqlite3_result_text(db, val.data(), val.length(), SQLITE_TRANSIENT); 219 | } 220 | // u16str_ref 221 | template<> 222 | struct has_sqlite_type : std::true_type {}; 223 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, u16str_ref val) { 224 | return sqlite3_bind_text16(stmt, inx, val.data(), sizeof(char16_t) * val.length(), SQLITE_TRANSIENT); 225 | } 226 | 227 | // Convert char* to string_view to trigger op<<(..., const str_ref ) 228 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const char16_t(&STR)[N]) { 229 | return sqlite3_bind_text16(stmt, inx, &STR[0], sizeof(char16_t) * (N-1), SQLITE_TRANSIENT); 230 | } 231 | 232 | inline std::u16string get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 233 | if ( sqlite3_column_type(stmt, inx) == SQLITE_NULL ) { 234 | return std::u16string(); 235 | } 236 | char16_t const * ptr = reinterpret_cast(sqlite3_column_text16(stmt, inx)); 237 | // call sqlite3_column_text16 explicitely before sqlite3_column_bytes16: it may convert the value to text 238 | return std::u16string(ptr, sqlite3_column_bytes16(stmt, inx)); 239 | } 240 | inline std::u16string get_val_from_db(sqlite3_value *value, result_type) { 241 | if ( sqlite3_value_type(value) == SQLITE_NULL ) { 242 | return std::u16string(); 243 | } 244 | char16_t const * ptr = reinterpret_cast(sqlite3_value_text16(value)); 245 | return std::u16string(ptr, sqlite3_value_bytes16(value)); 246 | } 247 | 248 | inline void store_result_in_db(sqlite3_context* db, u16str_ref val) { 249 | sqlite3_result_text16(db, val.data(), sizeof(char16_t) * val.length(), SQLITE_TRANSIENT); 250 | } 251 | 252 | // Other integer types 253 | template 254 | struct has_sqlite_type::value>::type> : std::true_type {}; 255 | 256 | template::value>::type> 257 | inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const Integral& val) { 258 | return bind_col_in_db(stmt, inx, static_cast(val)); 259 | } 260 | template::type>> 261 | inline void store_result_in_db(sqlite3_context* db, const Integral& val) { 262 | store_result_in_db(db, static_cast(val)); 263 | } 264 | template::value>::type> 265 | inline Integral get_col_from_db(sqlite3_stmt* stmt, int inx, result_type) { 266 | return get_col_from_db(stmt, inx, result_type()); 267 | } 268 | template::value>::type> 269 | inline Integral get_val_from_db(sqlite3_value *value, result_type) { 270 | return get_val_from_db(value, result_type()); 271 | } 272 | 273 | // vector 274 | template 275 | struct has_sqlite_type, SQLITE_BLOB, void> : std::true_type {}; 276 | 277 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const std::vector& vec) { 278 | void const* buf = reinterpret_cast(vec.data()); 279 | int bytes = vec.size() * sizeof(T); 280 | return sqlite3_bind_blob(stmt, inx, buf, bytes, SQLITE_TRANSIENT); 281 | } 282 | template inline void store_result_in_db(sqlite3_context* db, const std::vector& vec) { 283 | void const* buf = reinterpret_cast(vec.data()); 284 | int bytes = vec.size() * sizeof(T); 285 | sqlite3_result_blob(db, buf, bytes, SQLITE_TRANSIENT); 286 | } 287 | template inline std::vector get_col_from_db(sqlite3_stmt* stmt, int inx, result_type>) { 288 | if(sqlite3_column_type(stmt, inx) == SQLITE_NULL) { 289 | return {}; 290 | } 291 | T const* buf = reinterpret_cast(sqlite3_column_blob(stmt, inx)); 292 | int bytes = sqlite3_column_bytes(stmt, inx); 293 | return std::vector(buf, buf + bytes/sizeof(T)); 294 | } 295 | template inline std::vector get_val_from_db(sqlite3_value *value, result_type>) { 296 | if(sqlite3_value_type(value) == SQLITE_NULL) { 297 | return {}; 298 | } 299 | T const* buf = reinterpret_cast(sqlite3_value_blob(value)); 300 | int bytes = sqlite3_value_bytes(value); 301 | return std::vector(buf, buf + bytes/sizeof(T)); 302 | } 303 | 304 | /* for unique_ptr support */ 305 | template 306 | struct has_sqlite_type, Type, void> : has_sqlite_type {}; 307 | template 308 | struct has_sqlite_type, SQLITE_NULL, void> : std::true_type {}; 309 | 310 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const std::unique_ptr& val) { 311 | return val ? bind_col_in_db(stmt, inx, *val) : bind_col_in_db(stmt, inx, nullptr); 312 | } 313 | template inline std::unique_ptr get_col_from_db(sqlite3_stmt* stmt, int inx, result_type>) { 314 | if(sqlite3_column_type(stmt, inx) == SQLITE_NULL) { 315 | return nullptr; 316 | } 317 | return std::make_unique(get_col_from_db(stmt, inx, result_type())); 318 | } 319 | template inline std::unique_ptr get_val_from_db(sqlite3_value *value, result_type>) { 320 | if(sqlite3_value_type(value) == SQLITE_NULL) { 321 | return nullptr; 322 | } 323 | return std::make_unique(get_val_from_db(value, result_type())); 324 | } 325 | 326 | // std::optional support for NULL values 327 | #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT 328 | #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT 329 | template 330 | using optional = std::experimental::optional; 331 | #else 332 | template 333 | using optional = std::optional; 334 | #endif 335 | 336 | template 337 | struct has_sqlite_type, Type, void> : has_sqlite_type {}; 338 | template 339 | struct has_sqlite_type, SQLITE_NULL, void> : std::true_type {}; 340 | 341 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const optional& val) { 342 | return val ? bind_col_in_db(stmt, inx, *val) : bind_col_in_db(stmt, inx, nullptr); 343 | } 344 | template inline void store_result_in_db(sqlite3_context* db, const optional& val) { 345 | if(val) 346 | store_result_in_db(db, *val); 347 | else 348 | sqlite3_result_null(db); 349 | } 350 | 351 | template inline optional get_col_from_db(sqlite3_stmt* stmt, int inx, result_type>) { 352 | #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT 353 | if(sqlite3_column_type(stmt, inx) == SQLITE_NULL) { 354 | return std::experimental::nullopt; 355 | } 356 | return std::experimental::make_optional(get_col_from_db(stmt, inx, result_type())); 357 | #else 358 | if(sqlite3_column_type(stmt, inx) == SQLITE_NULL) { 359 | return std::nullopt; 360 | } 361 | return std::make_optional(get_col_from_db(stmt, inx, result_type())); 362 | #endif 363 | } 364 | template inline optional get_val_from_db(sqlite3_value *value, result_type>) { 365 | #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT 366 | if(sqlite3_value_type(value) == SQLITE_NULL) { 367 | return std::experimental::nullopt; 368 | } 369 | return std::experimental::make_optional(get_val_from_db(value, result_type())); 370 | #else 371 | if(sqlite3_value_type(value) == SQLITE_NULL) { 372 | return std::nullopt; 373 | } 374 | return std::make_optional(get_val_from_db(value, result_type())); 375 | #endif 376 | } 377 | #endif 378 | 379 | #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT 380 | namespace detail { 381 | template 382 | struct tag_trait : U { using tag = T; }; 383 | } 384 | 385 | template 386 | struct has_sqlite_type, Type, void> : std::disjunction>...> {}; 387 | 388 | namespace detail { 389 | template, Type>> 390 | inline std::variant variant_select_type(Callback &&callback) { 391 | if constexpr(first_compatible::value) 392 | return callback(result_type()); 393 | else 394 | throw errors::mismatch("The value is unsupported by this variant.", "", SQLITE_MISMATCH); 395 | } 396 | template inline decltype(auto) variant_select(int type, Callback &&callback) { 397 | switch(type) { 398 | case SQLITE_NULL: 399 | return variant_select_type(std::forward(callback)); 400 | case SQLITE_INTEGER: 401 | return variant_select_type(std::forward(callback)); 402 | case SQLITE_FLOAT: 403 | return variant_select_type(std::forward(callback)); 404 | case SQLITE_TEXT: 405 | return variant_select_type(std::forward(callback)); 406 | case SQLITE_BLOB: 407 | return variant_select_type(std::forward(callback)); 408 | } 409 | #ifdef _MSC_VER 410 | __assume(false); 411 | #else 412 | __builtin_unreachable(); 413 | #endif 414 | } 415 | } 416 | template inline int bind_col_in_db(sqlite3_stmt* stmt, int inx, const std::variant& val) { 417 | return std::visit([&](auto &&opt) {return bind_col_in_db(stmt, inx, std::forward(opt));}, val); 418 | } 419 | template inline void store_result_in_db(sqlite3_context* db, const std::variant& val) { 420 | std::visit([&](auto &&opt) {store_result_in_db(db, std::forward(opt));}, val); 421 | } 422 | template inline std::variant get_col_from_db(sqlite3_stmt* stmt, int inx, result_type>) { 423 | return detail::variant_select(sqlite3_column_type(stmt, inx), [&](auto v) { 424 | return std::variant(std::in_place_type, get_col_from_db(stmt, inx, v)); 425 | }); 426 | } 427 | template inline std::variant get_val_from_db(sqlite3_value *value, result_type>) { 428 | return detail::variant_select(sqlite3_value_type(value), [&](auto v) { 429 | return std::variant(std::in_place_type, get_val_from_db(value, v)); 430 | }); 431 | } 432 | #endif 433 | } 434 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/utility/function_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace sqlite { 7 | namespace utility { 8 | 9 | template struct function_traits; 10 | 11 | template 12 | struct function_traits : public function_traits< 13 | decltype(&std::remove_reference::type::operator()) 14 | > { }; 15 | 16 | template < 17 | typename ClassType, 18 | typename ReturnType, 19 | typename... Arguments 20 | > 21 | struct function_traits< 22 | ReturnType(ClassType::*)(Arguments...) const 23 | > : function_traits { }; 24 | 25 | /* support the non-const operator () 26 | * this will work with user defined functors */ 27 | template < 28 | typename ClassType, 29 | typename ReturnType, 30 | typename... Arguments 31 | > 32 | struct function_traits< 33 | ReturnType(ClassType::*)(Arguments...) 34 | > : function_traits { }; 35 | 36 | template < 37 | typename ReturnType, 38 | typename... Arguments 39 | > 40 | struct function_traits< 41 | ReturnType(*)(Arguments...) 42 | > { 43 | typedef ReturnType result_type; 44 | 45 | using argument_tuple = std::tuple; 46 | template 47 | using argument = typename std::tuple_element< 48 | Index, 49 | argument_tuple 50 | >::type; 51 | 52 | static const std::size_t arity = sizeof...(Arguments); 53 | }; 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/utility/uncaught_exceptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Consider that std::uncaught_exceptions is available if explicitly indicated 8 | // by the standard library, if compiler advertises full C++17 support or, as a 9 | // special case, for MSVS 2015+ (which doesn't define __cplusplus correctly by 10 | // default as of 2017.7 version and couldn't do it at all until it). 11 | #ifndef MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 12 | #ifdef __cpp_lib_uncaught_exceptions 13 | #define MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 14 | #elif __cplusplus >= 201703L 15 | #define MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 16 | #elif defined(_MSC_VER) && _MSC_VER >= 1900 17 | #define MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 18 | #endif 19 | #endif 20 | 21 | namespace sqlite { 22 | namespace utility { 23 | #ifdef MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 24 | class UncaughtExceptionDetector { 25 | public: 26 | operator bool() { 27 | return count != std::uncaught_exceptions(); 28 | } 29 | private: 30 | int count = std::uncaught_exceptions(); 31 | }; 32 | #else 33 | class UncaughtExceptionDetector { 34 | public: 35 | operator bool() { 36 | return std::uncaught_exception(); 37 | } 38 | }; 39 | #endif 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /hdr/sqlite_modern_cpp/utility/utf16_utf8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../errors.h" 8 | 9 | namespace sqlite { 10 | namespace utility { 11 | inline std::string utf16_to_utf8(u16str_ref input) { 12 | struct : std::codecvt { 13 | } codecvt; 14 | std::mbstate_t state{}; 15 | std::string result((std::max)(input.size() * 3 / 2, std::size_t(4)), '\0'); 16 | const char16_t *remaining_input = input.data(); 17 | std::size_t produced_output = 0; 18 | while(true) { 19 | char *used_output; 20 | switch(codecvt.out(state, remaining_input, input.data() + input.size(), 21 | remaining_input, &result[produced_output], 22 | &result[result.size() - 1] + 1, used_output)) { 23 | case std::codecvt_base::ok: 24 | result.resize(used_output - result.data()); 25 | return result; 26 | case std::codecvt_base::noconv: 27 | // This should be unreachable 28 | case std::codecvt_base::error: 29 | throw errors::invalid_utf16("Invalid UTF-16 input", ""); 30 | case std::codecvt_base::partial: 31 | if(used_output == result.data() + produced_output) 32 | throw errors::invalid_utf16("Unexpected end of input", ""); 33 | produced_output = used_output - result.data(); 34 | result.resize( 35 | result.size() 36 | + (std::max)((input.data() + input.size() - remaining_input) * 3 / 2, 37 | std::ptrdiff_t(4))); 38 | } 39 | } 40 | } 41 | } // namespace utility 42 | } // namespace sqlite 43 | -------------------------------------------------------------------------------- /tests/blob_example.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | TEST_CASE("Blob does work", "[blob]") { 11 | database db(":memory:"); 12 | 13 | db << "CREATE TABLE person (name TEXT, numbers BLOB);"; 14 | db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; 15 | db << "INSERT INTO person VALUES (?, ?)" << "jack" << vector { '1', '2', '3', '4'}; 16 | db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; 17 | 18 | vector numbers_bob; 19 | db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; 20 | 21 | REQUIRE(numbers_bob.size() == 4); 22 | REQUIRE((numbers_bob[0] == 1 && numbers_bob[1] == 2 && numbers_bob[2] == 3 && numbers_bob[3] == 4)); 23 | 24 | vector numbers_jack; 25 | db << "SELECT numbers from person where name = ?;" << "jack" >> numbers_jack; 26 | 27 | REQUIRE(numbers_jack.size() == 4); 28 | REQUIRE((numbers_jack[0] == '1' && numbers_jack[1] == '2' && numbers_jack[2] == '3' && numbers_jack[3] == '4')); 29 | 30 | 31 | vector numbers_sara; 32 | db << "SELECT numbers from person where name = ?;" << "sara" >> numbers_sara; 33 | 34 | REQUIRE(numbers_sara.size() == 4); 35 | REQUIRE((numbers_sara[0] == 1.0 && numbers_sara[1] == 2.0 && numbers_sara[2] == 3.0 && numbers_sara[3] == 4.0)); 36 | } 37 | -------------------------------------------------------------------------------- /tests/error_log.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace sqlite; 10 | using namespace std; 11 | 12 | struct TrackErrors { 13 | TrackErrors() 14 | : constraint_called{false}, primarykey_called{false} 15 | { 16 | error_log( 17 | [this](errors::constraint) { 18 | constraint_called = true; 19 | }, 20 | [this](errors::constraint_primarykey e) { 21 | primarykey_called = true; 22 | } 23 | // We are not registering the unique key constraint: 24 | // For a unique key error the first handler (errors::constraint) will be called instead. 25 | ); 26 | } 27 | 28 | bool constraint_called; 29 | bool primarykey_called; 30 | /* bool unique_called; */ 31 | }; 32 | 33 | // Run before main, before any other sqlite function. 34 | static TrackErrors track; 35 | 36 | 37 | TEST_CASE("error_log works", "[log]") { 38 | database db(":memory:"); 39 | db << "CREATE TABLE person (id integer primary key not null, name TEXT unique);"; 40 | 41 | SECTION("An extended error code gets called when registered") { 42 | try { 43 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 44 | // triger primarykey constraint of 'id' 45 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "bob"; 46 | } catch (const errors::constraint& e) { } 47 | REQUIRE(track.primarykey_called == true); 48 | REQUIRE(track.constraint_called == false); 49 | track.primarykey_called = false; 50 | } 51 | 52 | SECTION("Parent gets called when the exact error code is not registered") { 53 | try { 54 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 55 | // trigger unique constraint of 'name' 56 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 2 << "jack"; 57 | } catch (const errors::constraint& e) { } 58 | 59 | REQUIRE(track.primarykey_called == false); 60 | REQUIRE(track.constraint_called == true); 61 | track.constraint_called = false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/exception_dont_execute.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | 11 | TEST_CASE("Prepared statement will not execute on exceptions", "[prepared_statements]") { 12 | database db(":memory:"); 13 | db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);"; 14 | 15 | try { 16 | auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)"; 17 | throw 1; 18 | } catch (int) { } 19 | 20 | int count; 21 | db << "select count(*) from person" >> count; 22 | REQUIRE(count == 0); 23 | } 24 | -------------------------------------------------------------------------------- /tests/exception_dont_execute_nested.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | struct A { 11 | ~A() { 12 | database db(":memory:"); 13 | db << "CREATE TABLE person (id integer primary key not null, name TEXT not null);"; 14 | 15 | try { 16 | auto stmt = db << "INSERT INTO person (id,name) VALUES (?,?)"; 17 | throw 1; 18 | } catch (int) { 19 | } 20 | } 21 | }; 22 | 23 | TEST_CASE("Nested prepered statements wont execute", "[nested_prepared_statements]") { 24 | #ifdef MODERN_SQLITE_UNCAUGHT_EXCEPTIONS_SUPPORT 25 | try { 26 | A a; 27 | throw 1; 28 | } catch(int) { } 29 | #else 30 | #endif 31 | } 32 | -------------------------------------------------------------------------------- /tests/exceptions.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace sqlite; 9 | using namespace std; 10 | 11 | 12 | TEST_CASE("exceptions are thrown", "[exceptions]") { 13 | database db(":memory:"); 14 | db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; 15 | bool expception_thrown = false; 16 | std::string get_sql_result; 17 | 18 | #if SQLITE_VERSION_NUMBER >= 3014000 19 | std::string expedted_sql = "INSERT INTO person (id,name) VALUES (1,'jack')"; 20 | #else 21 | std::string expedted_sql = "INSERT INTO person (id,name) VALUES (?,?)"; 22 | #endif 23 | 24 | SECTION("Parent exception works") { 25 | try { 26 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 27 | // inserting again to produce error 28 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 29 | } catch (errors::constraint& e) { 30 | expception_thrown = true; 31 | get_sql_result = e.get_sql(); 32 | } 33 | 34 | REQUIRE(expception_thrown == true); 35 | REQUIRE(get_sql_result == expedted_sql); 36 | } 37 | 38 | SECTION("Extended exception works") { 39 | try { 40 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 41 | // inserting again to produce error 42 | db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; 43 | } catch (errors::constraint_primarykey& e) { 44 | expception_thrown = true; 45 | get_sql_result = e.get_sql(); 46 | } 47 | 48 | REQUIRE(expception_thrown == true); 49 | REQUIRE(get_sql_result == expedted_sql); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/flags.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | struct TmpFile { 11 | string fname; 12 | 13 | TmpFile(): fname("./flags.db") { } 14 | ~TmpFile() { remove(fname.c_str()); } 15 | }; 16 | #ifdef _WIN32 17 | #define OUR_UTF16 "UTF-16le" 18 | #elif BYTE_ORDER == BIG_ENDIAN 19 | #define OUR_UTF16 "UTF-16be" 20 | #else 21 | #define OUR_UTF16 "UTF-16le" 22 | #endif 23 | 24 | TEST_CASE("flags work", "[flags]") { 25 | TmpFile file; 26 | sqlite::sqlite_config cfg; 27 | std::string enc; 28 | SECTION("PRAGMA endcoding is UTF-8 for string literals") { 29 | database db(":memory:", cfg); 30 | db << "PRAGMA encoding;" >> enc; 31 | REQUIRE(enc == "UTF-8"); 32 | } 33 | SECTION("encoding is UTF-16 for u"" prefixed string literals") { 34 | database db(u":memory:", cfg); 35 | db << "PRAGMA encoding;" >> enc; 36 | REQUIRE(enc == OUR_UTF16); 37 | } 38 | SECTION("we can set encoding to UTF-8 with flags") { 39 | cfg.encoding = Encoding::UTF8; 40 | database db(u":memory:", cfg); 41 | db << "PRAGMA encoding;" >> enc; 42 | REQUIRE(enc == "UTF-8"); 43 | } 44 | SECTION("we can set encoding to UTF-16 with flags") { 45 | cfg.encoding = Encoding::UTF16; 46 | database db(u":memory:", cfg); 47 | db << "PRAGMA encoding;" >> enc; 48 | REQUIRE(enc == OUR_UTF16); 49 | } 50 | SECTION("we can set encoding to UTF-16 with flags for on disk databases") { 51 | cfg.encoding = Encoding::UTF16; 52 | database db(file.fname, cfg); 53 | db << "PRAGMA encoding;" >> enc; 54 | REQUIRE(enc == OUR_UTF16); 55 | 56 | } 57 | SECTION("READONLY flag works") { 58 | { 59 | database db(file.fname, cfg); 60 | db << "CREATE TABLE foo (a string);"; 61 | db << "INSERT INTO foo VALUES (?)" << "hello"; 62 | } 63 | 64 | cfg.flags = sqlite::OpenFlags::READONLY; 65 | database db(file.fname, cfg); 66 | 67 | string str; 68 | db << "SELECT a FROM foo;" >> str; 69 | 70 | REQUIRE(str == "hello"); 71 | 72 | bool failed = false; 73 | try { 74 | db << "INSERT INTO foo VALUES (?)" << "invalid"; 75 | } catch(errors::readonly&) { 76 | failed = true; 77 | } 78 | REQUIRE(failed == true); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/functions.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace sqlite; 7 | using namespace std; 8 | 9 | int add_integers(int i, int j) { 10 | return i+j; 11 | } 12 | TEST_CASE("sql functions work", "[functions]") { 13 | database db(":memory:"); 14 | 15 | db.define("my_new_concat", [](std::string i, std::string j) {return i+j;}); 16 | db.define("my_new_concat", [](std::string i, std::string j, std::string k) {return i+j+k;}); 17 | db.define("add_integers", &add_integers); 18 | 19 | std::string test1, test3; 20 | int test2 = 0; 21 | db << "select my_new_concat('Hello ','world!')" >> test1; 22 | db << "select add_integers(1,1)" >> test2; 23 | db << "select my_new_concat('a','b','c')" >> test3; 24 | 25 | REQUIRE(test1 == "Hello world!"); 26 | REQUIRE(test2 == 2); 27 | REQUIRE(test3 == "abc"); 28 | 29 | db.define("my_count", [](int &i, int) {++i;}, [](int &i) {return i;}); 30 | db.define("my_concat_aggregate", [](std::string &stored, std::string current) {stored += current;}, [](std::string &stored) {return stored;}); 31 | 32 | db << "create table countable(i, s)"; 33 | db << "insert into countable values(1, 'a')"; 34 | db << "insert into countable values(2, 'b')"; 35 | db << "insert into countable values(3, 'c')"; 36 | db << "select my_count(i) from countable" >> test2; 37 | db << "select my_concat_aggregate(s) from countable order by i" >> test3; 38 | 39 | REQUIRE(test2 == 3); 40 | REQUIRE(test3 == "abc"); 41 | 42 | db.define("tgamma", [](double i) {return std::tgamma(i);}); 43 | db << "CREATE TABLE numbers (number INTEGER);"; 44 | 45 | for(auto i=0; i!=10; ++i) 46 | db << "INSERT INTO numbers VALUES (?);" << i; 47 | 48 | db << "SELECT number, tgamma(number+1) FROM numbers;" >> [](double number, double factorial) { 49 | /* cout << number << "! = " << factorial << '\n'; */ 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /tests/functors.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace sqlite; 9 | using namespace std; 10 | 11 | struct tbl_functor { 12 | explicit tbl_functor(vector > &vec_) : vec(vec_) { } 13 | 14 | void operator() ( int id, string name) { 15 | vec.push_back(make_pair(id, move(name))); 16 | } 17 | vector > &vec; 18 | }; 19 | 20 | TEST_CASE("functors work", "[functors]") { 21 | database db(":memory:"); 22 | db << "CREATE TABLE tbl (id integer, name string);"; 23 | db << "INSERT INTO tbl VALUES (?, ?);" << 1 << "hello"; 24 | db << "INSERT INTO tbl VALUES (?, ?);" << 2 << "world"; 25 | 26 | vector > vec; 27 | db << "select id,name from tbl;" >> tbl_functor(vec); 28 | 29 | REQUIRE(vec.size() == 2); 30 | 31 | vec.clear(); 32 | 33 | tbl_functor functor(vec); 34 | db << "select id,name from tbl;" >> functor; 35 | 36 | REQUIRE(vec.size() == 2); 37 | REQUIRE(vec[0].first == 1); 38 | REQUIRE(vec[0].second == "hello"); 39 | } 40 | -------------------------------------------------------------------------------- /tests/lvalue_functor.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace sqlite; 7 | using namespace std; 8 | 9 | template 10 | struct builder { 11 | vector results; 12 | 13 | void operator()(AttrTypes... args) { 14 | results.emplace_back(std::forward(args)...); 15 | }; 16 | }; 17 | 18 | 19 | struct user { 20 | int age; 21 | string name; 22 | double weight; 23 | 24 | user(int age, string name, double weight) : age(age), name(name), weight(weight) { } 25 | 26 | static std::vector all(sqlite::database& db) { 27 | builder person_builder; 28 | db << "SELECT * FROM user;" 29 | >> person_builder; 30 | return std::move(person_builder.results); // move to avoid copying data ;-) 31 | }; 32 | }; 33 | 34 | TEST_CASE("lvalue functors work", "[lvalue_functor]") { 35 | 36 | database db(":memory:"); 37 | 38 | db << 39 | "create table if not exists user (" 40 | " age int," 41 | " name text," 42 | " weight real" 43 | ");"; 44 | 45 | db << "insert into user (age,name,weight) values (?,?,?);" << 20 << u"chandler" << 83.25; 46 | db << "insert into user (age,name,weight) values (?,?,?);" << 21 << u"monika" << 86.25; 47 | db << "insert into user (age,name,weight) values (?,?,?);" << 22 << u"ross" << 88.25; 48 | 49 | auto users = user::all(db); 50 | 51 | REQUIRE(users.size() == 3); 52 | } 53 | -------------------------------------------------------------------------------- /tests/mov_ctor.cc: -------------------------------------------------------------------------------- 1 | // Fixing https://github.com/SqliteModernCpp/sqlite_modern_cpp/issues/63 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | struct dbFront { 11 | std::unique_ptr storedProcedure; 12 | database db; 13 | dbFront(): db(":memory:") { 14 | db << "CREATE TABLE tbl (id integer, name string);"; 15 | // the temporary moved object should not run _execute() function on destruction. 16 | storedProcedure = std::make_unique( 17 | db << "INSERT INTO tbl VALUES (?, ?);" 18 | ); 19 | } 20 | }; 21 | 22 | 23 | TEST_CASE("database lifecycle", "move_ctor") { 24 | 25 | bool failed = false; 26 | try { dbFront dbf; } 27 | catch(const sqlite_exception& e) { failed = true; } 28 | catch(...) { failed = true; } 29 | 30 | REQUIRE(failed == false); 31 | } 32 | -------------------------------------------------------------------------------- /tests/named.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace sqlite; 6 | using namespace std; 7 | 8 | 9 | TEST_CASE("binding named parameters works", "[named]") { 10 | database db(":memory:"); 11 | 12 | db << "CREATE TABLE foo (a,b);"; 13 | 14 | int a = 1; 15 | db << "INSERT INTO foo VALUES (:first,:second)" << named_parameter(":second", 2) << named_parameter(":first", a); 16 | 17 | db << "SELECT b FROM foo WHERE a=?;" << 1 >> a; 18 | 19 | REQUIRE(a == 2); 20 | } -------------------------------------------------------------------------------- /tests/nullptr_uniqueptr.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | using namespace sqlite; 8 | 9 | TEST_CASE("nullptr & unique_ptr", "[null_ptr_unique_ptr]") { 10 | database db(":memory:"); 11 | db << "CREATE TABLE tbl (id integer,age integer, name string, img blob);"; 12 | db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 1 << 24 << "bob" << vector { 1, 2 , 3}; 13 | unique_ptr ptr_null; 14 | db << "INSERT INTO tbl VALUES (?, ?, ?, ?);" << 2 << nullptr << ptr_null << nullptr; 15 | 16 | db << "select age,name,img from tbl where id = 1" >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { 17 | REQUIRE(age_p != nullptr); 18 | REQUIRE(name_p != nullptr); 19 | REQUIRE(img_p != nullptr); 20 | }; 21 | 22 | db << "select age,name,img from tbl where id = 2" >> [](unique_ptr age_p, unique_ptr name_p, unique_ptr> img_p) { 23 | REQUIRE(age_p == nullptr); 24 | REQUIRE(name_p == nullptr); 25 | REQUIRE(img_p == nullptr); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /tests/prepared_statment.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace sqlite; 6 | using namespace std; 7 | 8 | TEST_CASE("prepared statements work", "[prepared_statement]") { 9 | database db(":memory:"); 10 | 11 | auto pps = db << "select ?"; // get a prepared parsed and ready statment 12 | 13 | int test = 4; 14 | pps << test; // set a bound var 15 | 16 | pps >> test; // execute statement 17 | 18 | REQUIRE(test == 4); 19 | 20 | pps << 4; // bind a rvalue 21 | pps++; // and execute 22 | 23 | pps << 8 >> test; 24 | 25 | REQUIRE(test == 8); 26 | 27 | auto pps2 = db << "select 1,2"; // multiple extract test 28 | 29 | pps2 >> [](int a, int b) { 30 | REQUIRE(a == 1); 31 | REQUIRE(b == 2); 32 | }; 33 | 34 | auto pps3 = db << "select ?,?,?"; 35 | 36 | test = 2; 37 | pps3 << 1 << test << 3 >> [](int a, int b, int c) { 38 | REQUIRE(a == 1); 39 | REQUIRE(b == 2); 40 | REQUIRE(c == 3); 41 | }; 42 | 43 | test = 1; 44 | db << "select ?,?" << test << 5 >> test; // and mow everything together 45 | REQUIRE(test == 1); 46 | 47 | test = 2; 48 | db << "select ?,?,?" << 1 << test << 3 >> [](int a, int b, int c) { 49 | REQUIRE(a == 1); 50 | REQUIRE(b == 2); 51 | REQUIRE(c == 3); 52 | }; 53 | 54 | db << "select ?" << test; // noVal 55 | db << "select ?,?" << test << 1; 56 | db << "select ?,?" << 1 << test; 57 | db << "select ?,?" << 1 << 1; 58 | db << "select ?,?" << test << test; 59 | 60 | db << "select ?" << test >> test; // lVal 61 | db << "select ?,?" << test << 1 >> test; 62 | db << "select ?,?" << 1 << test >> test; 63 | db << "select ?,?" << 1 << 1 >> test; 64 | db << "select ?,?" << test << test >> test; 65 | 66 | int q = 0; 67 | test = 1; 68 | db << "select ?" << test >> [&](int t) { q = t; }; // rVal 69 | REQUIRE(q == 1); 70 | 71 | db << "select ?,?" << test << 1 >> [&](int t, int p) { q = t + p; }; 72 | db << "select ?,?" << 1 << test >> [&](int t, int p) { q = t + p; }; 73 | db << "select ?,?" << 1 << 1 >> [&](int t, int p) { q = t + p; }; 74 | db << "select ?,?" << test << test >> [&](int t, int p) { q = t + p; }; 75 | 76 | db << "select ?,?,?" << test << 1 << test; // mix 77 | db << "select ?,?,?" << 1 << test << 1; 78 | db << "select ?,?,?" << 1 << 1 << test; 79 | db << "select ?,?,?" << 1 << 1 << 1; 80 | db << "select ?,?,?" << test << test << test; 81 | 82 | { 83 | auto pps4 = db << "select ?,?,?"; // reuse 84 | 85 | (pps4 << test << 1 << test)++; 86 | (pps4 << 1 << test << 1)++; 87 | (pps4 << 1 << 1 << test)++; 88 | (pps4 << 1 << 1 << 1)++; 89 | (pps4 << test << test << test)++; 90 | } 91 | 92 | { 93 | auto prep = db << "select ?"; 94 | 95 | prep << 5; 96 | prep.execute(); 97 | prep << 6; 98 | prep.execute(); 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /tests/readme_example.cc: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace sqlite; 7 | using namespace std; 8 | 9 | TEST_CASE("README Example Works", "[readme]") { 10 | 11 | database db(":memory:"); 12 | 13 | db << 14 | "create table if not exists user (" 15 | " _id integer primary key autoincrement not null," 16 | " age int," 17 | " name text," 18 | " weight real" 19 | ");"; 20 | 21 | db << "insert into user (age,name,weight) values (?,?,?);" 22 | << 20 23 | << u"bob" 24 | << 83.25; 25 | 26 | int age = 22; float weight = 68.5; string name = "jack"; 27 | db << u"insert into user (age,name,weight) values (?,?,?);" // utf16 query string 28 | << age 29 | << name 30 | << weight; 31 | 32 | REQUIRE(db.last_insert_rowid() != 0); 33 | 34 | db << "select age,name,weight from user where age > ? ;" 35 | << 21 36 | >> [&](int _age, string _name, double _weight) { 37 | REQUIRE((_age == age && _name == name)); 38 | }; 39 | 40 | for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 21) { 41 | int _age; 42 | string _name; 43 | double _weight; 44 | row >> _age >> _name >> _weight; 45 | REQUIRE((_age == age && _name == name)); 46 | } 47 | 48 | for(std::tuple row : db << "select age,name,weight from user where age > ? ;" << 21) { 49 | REQUIRE((std::get(row) == age && std::get(row) == name)); 50 | } 51 | 52 | // selects the count(*) from user table 53 | // note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string 54 | int count = 0; 55 | db << "select count(*) from user" >> count; 56 | REQUIRE(count == 2); 57 | 58 | db << "select age, name from user where _id=1;" >> tie(age, name); 59 | 60 | // this also works and the returned value will be automatically converted to string 61 | string str_count; 62 | db << "select count(*) from user" >> str_count; 63 | REQUIRE(str_count == string{"2"}); 64 | } 65 | -------------------------------------------------------------------------------- /tests/shared_connection.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | TEST_CASE("shared connections work fine", "[shared_connection]") { 11 | database db(":memory:"); 12 | 13 | { 14 | auto con = db.connection(); 15 | 16 | { 17 | database db2(con); 18 | int test = 0; 19 | db2 << "select 1" >> test; 20 | REQUIRE(test == 1); 21 | } 22 | 23 | int test = 0; 24 | db << "select 1" >> test; 25 | REQUIRE(test == 1); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/simple_examples.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace sqlite; 7 | using namespace std; 8 | 9 | TEST_CASE("simple examples", "[examples]") { 10 | database db(":memory:"); 11 | 12 | db << "CREATE TABLE foo (a integer, b string);\n"; 13 | db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello"; 14 | db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world"; 15 | 16 | string str; 17 | db << "SELECT b from FOO where a=?;" << 2L >> str; 18 | 19 | REQUIRE(str == "world"); 20 | 21 | std::string sql("select 1+1"); 22 | long test = 0; 23 | db << sql >> test; 24 | 25 | REQUIRE(test == 2); 26 | 27 | db << "UPDATE foo SET b=? WHERE a=?;" << "hi" << 1L; 28 | db << "SELECT b FROM foo WHERE a=?;" << 1L >> str; 29 | 30 | REQUIRE(str == "hi"); 31 | REQUIRE(db.rows_modified() == 1); 32 | 33 | db << "UPDATE foo SET b=?;" << "hello world"; 34 | 35 | REQUIRE(db.rows_modified() == 2); 36 | } 37 | -------------------------------------------------------------------------------- /tests/sqlcipher.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | using namespace sqlite; 8 | using namespace std; 9 | 10 | struct TmpFile 11 | { 12 | string fname; 13 | 14 | TmpFile(): fname("./sqlcipher.db") { } 15 | ~TmpFile() { remove(fname.c_str()); } 16 | }; 17 | 18 | TEST_CASE("sqlcipher works", "[sqlcipher]") { 19 | TmpFile file; 20 | sqlcipher_config config; 21 | { 22 | config.key = "DebugKey"; 23 | sqlcipher_database db(file.fname, config); 24 | 25 | db << "CREATE TABLE foo (a integer, b string);"; 26 | db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello"; 27 | db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world"; 28 | 29 | string str; 30 | db << "SELECT b from FOO where a=?;" << 2 >> str; 31 | 32 | REQUIRE(str == "world"); 33 | } 34 | 35 | bool failed = false; 36 | try { 37 | config.key = "DebugKey2"; 38 | sqlcipher_database db(file.fname, config); 39 | db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; 40 | } catch(const errors::notadb&) { 41 | failed = true; 42 | // Expected, wrong key 43 | } 44 | REQUIRE(failed == true); 45 | 46 | { 47 | config.key = "DebugKey"; 48 | sqlcipher_database db(file.fname, config); 49 | db.rekey("DebugKey2"); 50 | } 51 | { 52 | config.key = "DebugKey2"; 53 | sqlcipher_database db(file.fname, config); 54 | db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/std_optional.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace sqlite; 6 | using namespace std; 7 | 8 | #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT 9 | TEST_CASE("std::optional works", "[optional]") { 10 | database db(":memory:"); 11 | 12 | db << "drop table if exists test"; 13 | db << 14 | "create table if not exists test (" 15 | " id integer primary key," 16 | " val int" 17 | ");"; 18 | 19 | db << "insert into test(id,val) values(?,?)" << 1 << 5; 20 | db << "select id,val from test" >> [&](long long, sqlite::optional val) { 21 | REQUIRE(val); 22 | }; 23 | 24 | db << "delete from test where id = 1"; 25 | db << "insert into test(id,val) values(?,?)" << 1 << nullptr; 26 | db << "select id,val from test" >> [&](long long, sqlite::optional val) { 27 | REQUIRE(!val); 28 | }; 29 | 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /tests/string_view.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | #ifdef MODERN_SQLITE_STRINGVIEW_SUPPORT 6 | #include 7 | 8 | using namespace sqlite; 9 | TEST_CASE("std::string_view works", "[string_view]") { 10 | database db(":memory:"); 11 | db << "CREATE TABLE foo (a integer, b string);\n"; 12 | const std::string_view test1 = "null terminated string view"; 13 | db << "INSERT INTO foo VALUES (?, ?)" << 1 << test1; 14 | std::string str; 15 | db << "SELECT b from FOO where a=?;" << 1 >> str; 16 | REQUIRE(test1 == str); 17 | const char s[] = "hello world"; 18 | std::string_view test2(&s[0], 2); 19 | db << "INSERT INTO foo VALUES (?,?)" << 2 << test2; 20 | db << "SELECT b from FOO where a=?" << 2 >> str; 21 | REQUIRE(str == "he"); 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /tests/trycatchblocks.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace sqlite; 9 | using std::string; 10 | 11 | struct TmpFile { 12 | string fname; 13 | 14 | TmpFile(): fname("./trycatchblocks.db") {} 15 | ~TmpFile() { remove(fname.c_str()); } 16 | }; 17 | 18 | 19 | class DBInterface { 20 | database db; 21 | 22 | public: 23 | DBInterface( const string& fileName ) : db( fileName ) { } 24 | 25 | void LogRequest( const string& username, const string& ip, const string& request ) 26 | { 27 | try { 28 | auto timestamp = std::to_string( time( nullptr ) ); 29 | 30 | db << 31 | "create table if not exists log_request (" 32 | " _id integer primary key autoincrement not null," 33 | " username text," 34 | " timestamp text," 35 | " ip text," 36 | " request text" 37 | ");"; 38 | db << "INSERT INTO log_request (username, timestamp, ip, request) VALUES (?,?,?,?);" 39 | << username 40 | << timestamp 41 | << ip 42 | << request; 43 | } catch ( const std::exception& e ) { 44 | std::cout << e.what() << std::endl; 45 | } 46 | } 47 | 48 | bool TestData( void ) { 49 | try { 50 | string username, timestamp, ip, request; 51 | 52 | db << "select username, timestamp, ip, request from log_request where username = ?" 53 | << "test" 54 | >> std::tie(username, timestamp, ip, request); 55 | 56 | if ( username == "test" && ip == "127.0.0.1" && request == "hello world" ) { 57 | return true; 58 | } 59 | } catch ( const std::exception& e ) { 60 | std::cout << e.what() << std::endl; 61 | } 62 | 63 | return false; 64 | } 65 | }; 66 | 67 | TEST_CASE("try catch blocks", "[trycatchblocks]") { 68 | // -------------------------------------------------------------------------- 69 | // -- Test if writing to disk works properly from within a catch block. 70 | // -------------------------------------------------------------------------- 71 | try { 72 | throw "hello"; 73 | } 74 | catch ( ... ) { 75 | TmpFile tmpF; 76 | DBInterface interf(tmpF.fname); 77 | interf.LogRequest( "test", "127.0.0.1", "hello world" ); 78 | REQUIRE(interf.TestData() == true); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/variant.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace sqlite; 6 | using namespace std; 7 | 8 | #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT 9 | TEST_CASE("std::variant works", "[variant]") { 10 | database db(":memory:"); 11 | 12 | db << "CREATE TABLE foo (a);"; 13 | std::variant> v; 14 | 15 | v = 1; 16 | db << "INSERT INTO foo VALUES (?)" << v; 17 | 18 | v = "a"; 19 | db << "INSERT INTO foo VALUES (?)" << v; 20 | 21 | db << "SELECT a FROM foo WHERE a=?;" << 1 >> v; 22 | 23 | REQUIRE(v.index() == 1); 24 | REQUIRE(std::get<1>(v) == 1); 25 | 26 | db << "SELECT NULL" >> v; 27 | REQUIRE(!std::get<2>(v)); 28 | 29 | db << "SELECT 0.0" >> v; 30 | REQUIRE(std::get<2>(v)); 31 | } 32 | 33 | TEST_CASE("std::monostate is a nullptr substitute", "[monostate]") { 34 | database db(":memory:"); 35 | db << "CREATE TABLE foo (a);"; 36 | 37 | std::variant v; 38 | v=std::monostate(); 39 | db << "INSERT INTO foo VALUES (?)" << v; 40 | db << "INSERT INTO foo VALUES (?)" << "This isn't a monostate!"; 41 | 42 | bool found_null = false, 43 | found_string = false; 44 | 45 | db << "SELECT * FROM foo" 46 | >> [&](std::variant z) { 47 | if(z.index() == 0) { 48 | found_null = true; 49 | } else { 50 | found_string = true; 51 | } 52 | }; 53 | REQUIRE((found_null && found_string)); 54 | db << "SELECT NULL" >> v; 55 | REQUIRE(v.index() == 0); 56 | } 57 | #endif 58 | --------------------------------------------------------------------------------