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/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; +} diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 1490fd83..5e05164f 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 @@ -571,6 +591,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/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..74bd819a 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 @@ -65,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; @@ -80,6 +87,24 @@ 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); + const char *zFilename = sqlite3_db_filename(mSQLitePtr.get(), nullptr); + if (zFilename) + { + mFilename = zFilename; + } + 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) { 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 { 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) { {