Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build for use within a SQLite3 loadable extension #320

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions examples/example3_ext/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
24 changes: 24 additions & 0 deletions examples/example3_ext/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 16 additions & 0 deletions examples/example3_ext/README.md
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 31 additions & 0 deletions examples/example3_ext/src/example_extension.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Example using SQLiteCpp within the implementation of a SQLite3 run-time loadable extension
// SEE: https://sqlite.org/loadext.html
#include <sqlite3ext.h>
// 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 <SQLiteCpp/SQLiteCpp.h>
#include <iostream>

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();
}
}
26 changes: 26 additions & 0 deletions examples/example3_ext/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// driver program to load a SQLite3 extension

#include <sqlite3.h>
#include <iostream>

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;
}
23 changes: 22 additions & 1 deletion include/SQLiteCpp/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -571,6 +591,7 @@ class Database
private:
// TODO: perhaps switch to having Statement sharing a pointer to the Connexion
std::unique_ptr<sqlite3, Deleter> 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
};

Expand Down
5 changes: 5 additions & 0 deletions src/Backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@

#include <SQLiteCpp/Exception.h>

#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE)
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
extern "C" const sqlite3_api_routines *sqlite3_api;
#endif

namespace SQLite
{
Expand Down
5 changes: 5 additions & 0 deletions src/Column.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
*/
#include <SQLiteCpp/Column.h>

#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE)
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
extern "C" const sqlite3_api_routines *sqlite3_api;
#endif

#include <iostream>

Expand Down
25 changes: 25 additions & 0 deletions src/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
#include <SQLiteCpp/Exception.h>
#include <SQLiteCpp/Statement.h>

#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE)
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
extern "C" const sqlite3_api_routines *sqlite3_api;
#endif

#include <fstream>
#include <string.h>

Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
*/
#include <SQLiteCpp/Exception.h>

#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE)
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
extern "C" const sqlite3_api_routines *sqlite3_api;
#endif


namespace SQLite
Expand Down
5 changes: 5 additions & 0 deletions src/Statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
#include <SQLiteCpp/Assertion.h>
#include <SQLiteCpp/Exception.h>

#if !defined(SQLITECPP_IN_EXTENSION) || defined(SQLITE_CORE)
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
extern "C" const sqlite3_api_routines *sqlite3_api;
#endif

namespace SQLite
{
Expand Down
22 changes: 22 additions & 0 deletions tests/Database_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
{
Expand Down