From d21b605bd686483bf433ead135a221af6aaf62a9 Mon Sep 17 00:00:00 2001 From: Mike Lin Date: Fri, 22 Jan 2021 19:51:11 -1000 Subject: [PATCH 1/4] SQLITECPP_IN_EXTENSION option controlling linking and use of sqlite3ext.h --- CMakeLists.txt | 10 ++++++++-- src/Backup.cpp | 5 +++++ src/Column.cpp | 5 +++++ src/Database.cpp | 6 ++++++ src/Exception.cpp | 5 +++++ src/Statement.cpp | 5 +++++ 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b651f472..123f84cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,13 @@ endif () ## Build provided copy of SQLite3 C library ## option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON) -if (SQLITECPP_INTERNAL_SQLITE) +option(SQLITECPP_IN_EXTENSION "Use SQLiteCpp in the implementation of a SQLite3 loadable extension" OFF) +if(SQLITECPP_IN_EXTENSION) + # To use within a SQLite3 loadable extension, we must not target_link_libraries() SQLite3 itself; the + # host program will already have loaded it (possibly a statically-linked version) and we'll receive its + # API pointers via sqlite3_extension_init(). + target_compile_definitions(SQLiteCpp PUBLIC SQLITECPP_IN_EXTENSION) +elseif (SQLITECPP_INTERNAL_SQLITE) message(STATUS "Compile sqlite3 from source in subdirectory") option(SQLITE_ENABLE_JSON1 "Enable JSON1 extension when building internal sqlite3 library." ON) # build the SQLite3 C library (for ease of use/compatibility) versus Linux sqlite3-dev package @@ -285,7 +291,7 @@ else (SQLITECPP_INTERNAL_SQLITE) target_link_libraries(SQLiteCpp PRIVATE ${sqlcipher_LIBRARY}) endif() endif() -endif (SQLITECPP_INTERNAL_SQLITE) +endif (SQLITECPP_IN_EXTENSION) # Link target with pthread and dl for Unix if (UNIX) diff --git a/src/Backup.cpp b/src/Backup.cpp index 17a13e2b..3e2ba335 100644 --- a/src/Backup.cpp +++ b/src/Backup.cpp @@ -13,7 +13,12 @@ #include +#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE) #include +#else +#include +extern "C" const sqlite3_api_routines *sqlite3_api; +#endif namespace SQLite { diff --git a/src/Column.cpp b/src/Column.cpp index b3960fef..6b626b1b 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -10,7 +10,12 @@ */ #include +#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE) #include +#else +#include +extern "C" const sqlite3_api_routines *sqlite3_api; +#endif #include diff --git a/src/Database.cpp b/src/Database.cpp index 7b81f8b9..d9f5bd53 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -15,7 +15,13 @@ #include #include +#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE) #include +#else +#include +extern "C" const sqlite3_api_routines *sqlite3_api; +#endif + #include #include diff --git a/src/Exception.cpp b/src/Exception.cpp index 2840787e..cdd5e2a8 100644 --- a/src/Exception.cpp +++ b/src/Exception.cpp @@ -10,7 +10,12 @@ */ #include +#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE) #include +#else +#include +extern "C" const sqlite3_api_routines *sqlite3_api; +#endif namespace SQLite diff --git a/src/Statement.cpp b/src/Statement.cpp index 8ab68624..0f838dc4 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -15,7 +15,12 @@ #include #include +#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE) #include +#else +#include +extern "C" const sqlite3_api_routines *sqlite3_api; +#endif namespace SQLite { From cfc921c524328ba32b9207f7cb9096ae91bc28e4 Mon Sep 17 00:00:00 2001 From: Mike Lin Date: Fri, 22 Jan 2021 20:30:43 -1000 Subject: [PATCH 2/4] Database() constructor to wrap an existing sqlite3*, leaving it open on destruct --- include/SQLiteCpp/Database.h | 30 ++++++++++++++++++++++++------ src/Database.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 1490fd83..00bb3a4a 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -201,6 +201,21 @@ class Database #endif // c++17 + /** + * @brief Wrap an existing sqlite3* connection opened by other means. + * + * When the Database object is constructed as a wrapper, its destruction does NOT automatically + * sqlite3_close() the connection. In this case (only), Statement objects may outlive the Database object with + * which they were constructed, so long as the underlying connection remains open. + * + * @param[in] apSQLite Existing sqlite3* connection to be wrapped + * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) + * + * @throw SQLite::Exception in case of error + */ + Database(sqlite3* apSQLite, + const int aBusyTimeoutMs = 0); + // Database is non-copyable Database(const Database&) = delete; Database& operator=(const Database&) = delete; @@ -217,7 +232,12 @@ class Database * * @warning assert in case of error */ - ~Database() = default; + ~Database() + { + if (!mCloseOnDestruct) { + mSQLitePtr.release(); // prevent Deleter + } + } // Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion. struct Deleter @@ -414,7 +434,7 @@ class Database /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). const char* getErrorMsg() const noexcept; - /// Return the filename used to open the database. + /// Return the filename used to open the database; empty if the Database wrapped existing sqlite3* const std::string& getFilename() const noexcept { return mFilename; @@ -536,10 +556,7 @@ class Database static Header getHeaderInfo(const std::string& aFilename); // Parse SQLite header data from a database file. - Header getHeaderInfo() - { - return getHeaderInfo(mFilename); - } + Header getHeaderInfo(); /** * @brief BackupType for the backup() method @@ -571,6 +588,7 @@ class Database private: // TODO: perhaps switch to having Statement sharing a pointer to the Connexion std::unique_ptr mSQLitePtr; ///< Pointer to SQLite Database Connection Handle + bool mCloseOnDestruct; ///< true iff ~Database() is to use sqlite3_close() on mSQLitePtr std::string mFilename; ///< UTF-8 filename used to open the database }; diff --git a/src/Database.cpp b/src/Database.cpp index d9f5bd53..3504d45b 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -71,6 +71,7 @@ Database::Database(const char* apFilename, const int aFlags /* = SQLite::OPEN_READONLY*/, const int aBusyTimeoutMs /* = 0 */, const char* apVfs /* = nullptr*/) : + mCloseOnDestruct(true), mFilename(apFilename) { sqlite3* handle; @@ -86,6 +87,19 @@ Database::Database(const char* apFilename, } } +// Wrap an existing sqlite3* connection opened by other means. +Database::Database(sqlite3* apSQLite, + const int aBusyTimeoutMs /* = 0 */) : + mCloseOnDestruct(false) +{ + SQLITECPP_ASSERT(apSQLite != nullptr, "Database(nullptr)"); + mSQLitePtr.reset(apSQLite); + if (aBusyTimeoutMs > 0) + { + setBusyTimeout(aBusyTimeoutMs); + } +} + // Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion. void Database::Deleter::operator()(sqlite3* apSQLite) { @@ -432,6 +446,18 @@ Header Database::getHeaderInfo(const std::string& aFilename) return h; } +Header Database::getHeaderInfo() +{ + if (!mFilename.empty()) + { + return getHeaderInfo(mFilename); + } + const char *zFilename = sqlite3_db_filename(mSQLitePtr.get(), nullptr); + return getHeaderInfo(std::string(zFilename ? zFilename : "")); +} + + + void Database::backup(const char* apFilename, BackupType aType) { // Open the database file identified by apFilename From 49a568f15d025ddcd61d54c437a4c5ef773d2389 Mon Sep 17 00:00:00 2001 From: Mike Lin Date: Fri, 22 Jan 2021 21:54:46 -1000 Subject: [PATCH 3/4] add example using SQLiteCpp within implementation of a loadable extension --- examples/example3_ext/.gitignore | 1 + examples/example3_ext/CMakeLists.txt | 24 ++++++++++++++ examples/example3_ext/README.md | 16 ++++++++++ .../example3_ext/src/example_extension.cpp | 31 +++++++++++++++++++ examples/example3_ext/src/main.cpp | 26 ++++++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 examples/example3_ext/.gitignore create mode 100644 examples/example3_ext/CMakeLists.txt create mode 100644 examples/example3_ext/README.md create mode 100644 examples/example3_ext/src/example_extension.cpp create mode 100644 examples/example3_ext/src/main.cpp diff --git a/examples/example3_ext/.gitignore b/examples/example3_ext/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/examples/example3_ext/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/examples/example3_ext/CMakeLists.txt b/examples/example3_ext/CMakeLists.txt new file mode 100644 index 00000000..20d3c3dd --- /dev/null +++ b/examples/example3_ext/CMakeLists.txt @@ -0,0 +1,24 @@ +# Example building a SQLite3 loadable extension that uses SQLiteCpp internally +cmake_minimum_required(VERSION 3.1) # for "CMAKE_CXX_STANDARD" version +project(SQLiteCpp_ExampleExtension VERSION 1.0) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# KEY OPTION HERE: builds SQLiteCpp for use within the implementation of a loadable extension +set(SQLITECPP_IN_EXTENSION ON CACHE BOOL "" FORCE) + +set(SQLITECPP_RUN_CPPCHECK OFF CACHE BOOL "" FORCE) +set(SQLITECPP_RUN_CPPLINT OFF CACHE BOOL "" FORCE) +set(SQLITECPP_USE_STATIC_RUNTIME OFF CACHE BOOL "" FORCE) +set(SQLITECPP_USE_STATIC_RUNTIME OFF CACHE BOOL "" FORCE) +add_subdirectory(../.. SQLiteCpp) # out-of-source build requires explicit subdir name for compilation artifacts + +add_library(example SHARED src/example_extension.cpp) +target_link_libraries(example SQLiteCpp) + +# Compile driver program that'll load the extension. It links sqlite3 statically, so our extension library +# mustn't itself link sqlite3, either statically or dynamically (that's one thing accomplished by +# SQLITECPP_IN_EXTENSION) +add_executable(example_driver src/main.cpp) +target_link_libraries(example_driver -static sqlite3 dl pthread) diff --git a/examples/example3_ext/README.md b/examples/example3_ext/README.md new file mode 100644 index 00000000..f9a7a4ac --- /dev/null +++ b/examples/example3_ext/README.md @@ -0,0 +1,16 @@ +This example demonstrates how to use SQLiteCpp *within the implementation* of a +[SQLite3 loadable extension](https://sqlite.org/loadext.html). Change into this directory and + +``` +cmake -B build . +cmake --build build +build/example_driver $(pwd)/build/libexample.so +``` + +*(replace .so with .dylib or .dll if appropriate)* + +This should print `it works 42`. Here the `example_driver` program links SQLite3 *statically*, so +it's important to ensure that SQLiteCpp inside the extension will use that "copy" of SQLite3 rather +than trying to dynamically link another one. See [CMakeLists.txt](CMakeLists.txt) for the key CMake +option that ensures this, and [src/example_extension.cpp](src/example_extension.cpp) for some +necessary boilerplate. diff --git a/examples/example3_ext/src/example_extension.cpp b/examples/example3_ext/src/example_extension.cpp new file mode 100644 index 00000000..5040ed3e --- /dev/null +++ b/examples/example3_ext/src/example_extension.cpp @@ -0,0 +1,31 @@ +// Example using SQLiteCpp within the implementation of a SQLite3 run-time loadable extension +// SEE: https://sqlite.org/loadext.html +#include +// When SQLiteCpp is built with option SQLITECPP_IN_EXTENSION=ON, its compiled objects will expect +// to find an extern "C" symbol declared by the following macro in the extension implementation. +extern "C" { +SQLITE_EXTENSION_INIT1 +} +#include +#include + +extern "C" int sqlite3_example_init(sqlite3 *rawDb, char **pzErrMsg, + const sqlite3_api_routines *pApi) { + SQLITE_EXTENSION_INIT2(pApi); + + try { + // temporarily wrap rawDb as a SQLite::Database so we can use SQLiteCpp's conveniences + SQLite::Database db(rawDb); + SQLite::Statement stmt(db, "SELECT 'it works ' || ?"); + stmt.bind(1, 42); + if (stmt.executeStep()) { + std::cout << stmt.getColumn(0).getString() << std::endl; + } + // In a real extension we'd now register custom functions, virtual tables, or VFS objects, + // any of which might also want to use SQLiteCpp wrappers for the raw connection. + return SQLITE_OK; + } catch (SQLite::Exception& exn) { + std::cerr << exn.getErrorStr() << std::endl; + return exn.getErrorCode(); + } +} diff --git a/examples/example3_ext/src/main.cpp b/examples/example3_ext/src/main.cpp new file mode 100644 index 00000000..da742f0e --- /dev/null +++ b/examples/example3_ext/src/main.cpp @@ -0,0 +1,26 @@ +// driver program to load a SQLite3 extension + +#include +#include + +int main(int argc, char **argv) { + if (argc < 2) { + std::cerr << "Usage: example_driver EXTENSION_ABSOLUTE_PATH" << std::endl; + return -1; + } + sqlite3 *db; + if (sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK) { + std::cerr << "sqlite3_open_v2() failed" << std::endl; + return -1; + } + char *zErrMsg = nullptr; + if (sqlite3_load_extension(db, argv[1], nullptr, &zErrMsg) != SQLITE_OK) { + std::cerr << "sqlite3_load_extension() failed"; + if (zErrMsg) { + std::cerr << ": " << zErrMsg << std::endl; + } + std::cerr << std::endl; + return -1; + } + return 0; +} From 6d089fc64a47a63a5894c948846a28a012ec45a0 Mon Sep 17 00:00:00 2001 From: Mike Lin Date: Sun, 30 May 2021 16:41:49 -1000 Subject: [PATCH 4/4] add unit test & tidy diff --- include/SQLiteCpp/Database.h | 7 +++++-- src/Database.cpp | 17 +++++------------ tests/Database_test.cpp | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 00bb3a4a..5e05164f 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -434,7 +434,7 @@ class Database /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). const char* getErrorMsg() const noexcept; - /// Return the filename used to open the database; empty if the Database wrapped existing sqlite3* + /// Return the filename used to open the database. const std::string& getFilename() const noexcept { return mFilename; @@ -556,7 +556,10 @@ class Database static Header getHeaderInfo(const std::string& aFilename); // Parse SQLite header data from a database file. - Header getHeaderInfo(); + Header getHeaderInfo() + { + return getHeaderInfo(mFilename); + } /** * @brief BackupType for the backup() method diff --git a/src/Database.cpp b/src/Database.cpp index 3504d45b..74bd819a 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -94,6 +94,11 @@ Database::Database(sqlite3* apSQLite, { SQLITECPP_ASSERT(apSQLite != nullptr, "Database(nullptr)"); mSQLitePtr.reset(apSQLite); + const char *zFilename = sqlite3_db_filename(mSQLitePtr.get(), nullptr); + if (zFilename) + { + mFilename = zFilename; + } if (aBusyTimeoutMs > 0) { setBusyTimeout(aBusyTimeoutMs); @@ -446,18 +451,6 @@ Header Database::getHeaderInfo(const std::string& aFilename) return h; } -Header Database::getHeaderInfo() -{ - if (!mFilename.empty()) - { - return getHeaderInfo(mFilename); - } - const char *zFilename = sqlite3_db_filename(mSQLitePtr.get(), nullptr); - return getHeaderInfo(std::string(zFilename ? zFilename : "")); -} - - - void Database::backup(const char* apFilename, BackupType aType) { // Open the database file identified by apFilename diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 2493a0d4..75df453c 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -121,6 +121,28 @@ TEST(Database, createCloseReopen) remove("test.db3"); } +TEST(Database, wrapper) +{ + remove("test.db4"); + // Create a new database using SQLite3 directly + sqlite3 *dbconn = nullptr; + int rc = sqlite3_open_v2("test.db4", &dbconn, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, nullptr); + EXPECT_EQ(rc, SQLITE_OK); + { + // instantiate SQLite::Database wrapper + SQLite::Database db(dbconn, 5000); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + std::string filename = db.getFilename(); + EXPECT_STREQ(filename.substr(filename.rfind('/')+1).c_str(), "test.db4"); + } + // dbconn remains open after db destruction + rc = sqlite3_close(dbconn); + EXPECT_EQ(rc, SQLITE_OK); + remove("test.db4"); +} + TEST(Database, inMemory) { {