From 8051b048db16463927c6b6e21efa0837bda07c08 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 14 Jul 2023 10:18:29 +0200 Subject: [PATCH] Add a minimal loadable extension example --- .github/workflows/main.yml | 3 +++ Cargo.toml | 5 ++++ examples/loadable_extension.rs | 48 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 examples/loadable_extension.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2cd3b8c..abed5ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,6 +65,9 @@ jobs: - run: cargo test --features 'bundled-full session buildtime_bindgen' --all-targets --workspace --verbose - run: cargo test --features 'bundled-full session buildtime_bindgen' --doc --workspace --verbose + - name: loadable extension + run: cargo build --example loadable_extension --features "loadable_extension modern_sqlite functions vtab trace" + # TODO: move into own action for better caching - name: Static build # Do we expect this to work / should we test with gnu toolchain? diff --git a/Cargo.toml b/Cargo.toml index 38f5937..ae4df7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,6 +157,11 @@ harness = false name = "exec" harness = false +[[example]] +name = "loadable_extension" +crate-type = ["cdylib"] +required-features = ["loadable_extension", "modern_sqlite", "functions", "vtab", "trace"] + [package.metadata.docs.rs] features = ["modern-full"] all-features = false diff --git a/examples/loadable_extension.rs b/examples/loadable_extension.rs new file mode 100644 index 0000000..809d42f --- /dev/null +++ b/examples/loadable_extension.rs @@ -0,0 +1,48 @@ +//! Adaptation of https://sqlite.org/loadext.html#programming_loadable_extensions +use std::os::raw::{c_char, c_int}; + +use rusqlite::ffi; +use rusqlite::functions::FunctionFlags; +use rusqlite::types::{ToSqlOutput, Value}; +use rusqlite::{to_sqlite_error, Connection, Result}; + +/// # build +/// ```sh +/// cargo build --example loadable_extension --features "loadable_extension modern_sqlite functions vtab trace" +/// ``` +/// # test +/// ```sh +/// sqlite> .log on +/// sqlite> .load target/debug/examples/libloadable_extension.so +/// (28) Rusqlite extension initialized +/// sqlite> SELECT rusqlite_test_function(); +/// Rusqlite extension loaded correctly! +/// ``` +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn sqlite3_extension_init( + db: *mut ffi::sqlite3, + pz_err_msg: *mut *mut c_char, + p_api: *mut ffi::sqlite3_api_routines, +) -> c_int { + if let Err(err) = extension_init(db, p_api) { + return unsafe { to_sqlite_error(&err, pz_err_msg) }; + } + ffi::SQLITE_OK +} + +fn extension_init(db: *mut ffi::sqlite3, p_api: *mut ffi::sqlite3_api_routines) -> Result<()> { + let db = unsafe { Connection::extension_init2(db, p_api)? }; + db.create_scalar_function( + "rusqlite_test_function", + 0, + FunctionFlags::SQLITE_DETERMINISTIC, + |_ctx| { + Ok(ToSqlOutput::Owned(Value::Text( + "Rusqlite extension loaded correctly!".to_string(), + ))) + }, + )?; + rusqlite::trace::log(ffi::SQLITE_WARNING, "Rusqlite extension initialized"); + Ok(()) +}