From fb96252f07553bb0bb4ae8e0d7edce7bc0a8402c Mon Sep 17 00:00:00 2001 From: Aadi Desai <21363892+supleed2@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:29:41 +0000 Subject: [PATCH 1/2] Add SQLite extension entrypoint config to `sqlx.toml`, update SQLite extension example --- .../sqlite/extension/download-extension.sh | 6 ++- .../migrations/20251115215857_uuid.sql | 10 ++++ examples/sqlite/extension/sqlx.toml | 7 ++- examples/sqlite/extension/src/main.rs | 50 ++++++++++++------- sqlx-core/src/config/drivers.rs | 14 +++++- sqlx-sqlite/src/options/mod.rs | 5 +- 6 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 examples/sqlite/extension/migrations/20251115215857_uuid.sql diff --git a/examples/sqlite/extension/download-extension.sh b/examples/sqlite/extension/download-extension.sh index ce7f23a486..83829b3ae9 100755 --- a/examples/sqlite/extension/download-extension.sh +++ b/examples/sqlite/extension/download-extension.sh @@ -6,4 +6,8 @@ # directory on the library search path, either by using the system # package manager or by compiling and installing it yourself. -mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so +mkdir /tmp/sqlite3-lib && \ + wget -O /tmp/sqlean-linux-x64.zip https://github.com/nalgeon/sqlean/releases/download/0.28.0/sqlean-linux-x64.zip && \ + unzip /tmp/sqlean-linux-x64.zip -d /tmp/sqlite3-lib && \ + mv /tmp/sqlite3-lib/uuid.so /tmp/sqlite3-lib/uuid_renamed.so && \ + rm /tmp/sqlean-linux-x64.zip diff --git a/examples/sqlite/extension/migrations/20251115215857_uuid.sql b/examples/sqlite/extension/migrations/20251115215857_uuid.sql new file mode 100644 index 0000000000..c0a8673b4b --- /dev/null +++ b/examples/sqlite/extension/migrations/20251115215857_uuid.sql @@ -0,0 +1,10 @@ +create table uuids (uuid text); + +-- The `uuid4` function is provided by the +-- [uuid](https://github.com/nalgeon/sqlean/blob/main/docs/uuid.md) +-- sqlite extension, and so this migration can not run if that +-- extension is not loaded. +insert into uuids (uuid) values + (uuid4()), + (uuid4()), + (uuid4()); diff --git a/examples/sqlite/extension/sqlx.toml b/examples/sqlite/extension/sqlx.toml index 7c67dd160e..ada65b1a25 100644 --- a/examples/sqlite/extension/sqlx.toml +++ b/examples/sqlite/extension/sqlx.toml @@ -1,4 +1,4 @@ -[common.drivers.sqlite] +[drivers.sqlite] # Including the full path to the extension is somewhat unusual, # because normally an extension will be installed in a standard # directory which is part of the library search path. If that were the @@ -9,4 +9,7 @@ # * Provide the full path the the extension, as seen below. # * Add the non-standard location to the library search path, which on # Linux means adding it to the LD_LIBRARY_PATH environment variable. -unsafe-load-extensions = ["/tmp/sqlite3-lib/ipaddr"] +unsafe-load-extensions = [ + "/tmp/sqlite3-lib/ipaddr", + { path = "/tmp/sqlite3-lib/uuid_renamed", entrypoint = "sqlite3_uuid_init" }, +] diff --git a/examples/sqlite/extension/src/main.rs b/examples/sqlite/extension/src/main.rs index ee859c55b8..15a5b01ab8 100644 --- a/examples/sqlite/extension/src/main.rs +++ b/examples/sqlite/extension/src/main.rs @@ -7,24 +7,32 @@ use sqlx::{ #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { - let opts = SqliteConnectOptions::from_str(&std::env::var("DATABASE_URL")?)? - // The sqlx.toml file controls loading extensions for the CLI - // and for the query checking macros, *not* for the - // application while it's running. Thus, if we want the - // extension to be available during program execution, we need - // to load it. - // - // Note that while in this case the extension path is the same - // when checking the program (sqlx.toml) and when running it - // (here), this is not required. The runtime environment can - // be entirely different from the development one. - // - // The extension can be described with a full path, as seen - // here, but in many cases that will not be necessary. As long - // as the extension is installed in a directory on the library - // search path, it is sufficient to just provide the extension - // name, like "ipaddr" - .extension("/tmp/sqlite3-lib/ipaddr"); + let opts = SqliteConnectOptions::from_str(&std::env::var("DATABASE_URL")?)?; + // The sqlx.toml file controls loading extensions for the CLI + // and for the query checking macros, *not* for the + // application while it's running. Thus, if we want the + // extension to be available during program execution, we need + // to load it. + // + // Note that while in this case the extension paths are the + // same when checking the program (sqlx.toml) and when running + // it (here), this is not required. The runtime environment + // can be entirely different from the development one. + // + // The extension can be described with a full path, as seen + // here, but in many cases that will not be necessary. As long + // as the extension is installed in a directory on the library + // search path, it is sufficient to just provide the extension + // name, like "ipaddr" + let opts = unsafe { opts.extension("/tmp/sqlite3-lib/ipaddr") }; + // The entrypoint for an extension is usually inferred as + // `sqlite3_extension_init` or `sqlite3_X_init` where X is the + // lowercase, ASCII-only equivalent of the filename. For the + // extension below, this would be `sqlite3_uuidrenamed_init`. + // The entrypoint can instead be explicitly provided. + let opts = unsafe { + opts.extension_with_entrypoint("/tmp/sqlite3-lib/uuid_renamed", "sqlite3_uuid_init") + }; let db = SqlitePool::connect_with(opts).await?; @@ -41,7 +49,11 @@ async fn main() -> anyhow::Result<()> { .execute(&db) .await?; - println!("Query which requires the extension was successfully executed."); + query!("insert into uuids (uuid) values (uuid4())") + .execute(&db) + .await?; + + println!("Queries which require the extensions were successfully executed."); Ok(()) } diff --git a/sqlx-core/src/config/drivers.rs b/sqlx-core/src/config/drivers.rs index 5019f1f9f6..6ad2975c48 100644 --- a/sqlx-core/src/config/drivers.rs +++ b/sqlx-core/src/config/drivers.rs @@ -102,7 +102,19 @@ pub struct SqliteConfig { /// [common.drivers.sqlite] /// unsafe-load-extensions = ["uuid", "vsv"] /// ``` - pub unsafe_load_extensions: Vec, + pub unsafe_load_extensions: Vec, +} + +/// Extension for the SQLite database driver. +#[derive(Debug)] +#[cfg_attr( + feature = "sqlx-toml", + derive(serde::Deserialize), + serde(untagged, deny_unknown_fields) +)] +pub enum SqliteExtension { + Path(String), + PathWithEntrypoint { path: String, entrypoint: String }, } /// Configuration for external database drivers. diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index b2849f243c..8e58f4db5a 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -646,7 +646,10 @@ impl SqliteConnectOptions { #[cfg(feature = "load-extension")] for extension in &config.unsafe_load_extensions { // SAFETY: the documentation warns the user about loading extensions - self = unsafe { self.extension(extension.clone()) }; + match extension { + config::drivers::SqliteExtension::Path(path) => self = unsafe { self.extension(path.clone()) }, + config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => self = unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) }, + } } #[cfg(not(feature = "load-extension"))] From 1cc8d4aa1ca3f28e510295de62e449afac431513 Mon Sep 17 00:00:00 2001 From: Aadi Desai <21363892+supleed2@users.noreply.github.com> Date: Sun, 16 Nov 2025 17:27:46 +0000 Subject: [PATCH 2/2] Fix tests & formatting --- sqlx-core/src/config/drivers.rs | 2 +- sqlx-core/src/config/reference.toml | 9 ++++++--- sqlx-core/src/config/tests.rs | 11 ++++++++++- sqlx-sqlite/src/options/mod.rs | 11 ++++++++--- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/sqlx-core/src/config/drivers.rs b/sqlx-core/src/config/drivers.rs index 6ad2975c48..c3416541cd 100644 --- a/sqlx-core/src/config/drivers.rs +++ b/sqlx-core/src/config/drivers.rs @@ -106,7 +106,7 @@ pub struct SqliteConfig { } /// Extension for the SQLite database driver. -#[derive(Debug)] +#[derive(Debug, PartialEq)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), diff --git a/sqlx-core/src/config/reference.toml b/sqlx-core/src/config/reference.toml index 00f0af9285..618ac23c91 100644 --- a/sqlx-core/src/config/reference.toml +++ b/sqlx-core/src/config/reference.toml @@ -44,7 +44,10 @@ database-url-var = "FOO_DATABASE_URL" # It is not possible to provide a truly safe version of this API. # # Use this field with care, and only load extensions that you trust. -unsafe-load-extensions = ["uuid", "vsv"] +unsafe-load-extensions = [ + "uuid", + { path = "vsv_renamed", entrypoint = "sqlite3_vsv_init" }, +] # Configure external drivers in macros and sqlx-cli. # @@ -90,8 +93,8 @@ numeric = "rust_decimal" # or not. They only override the inner type used. [macros.type-overrides] # Override a built-in type (map all `UUID` columns to `crate::types::MyUuid`) -# Note: currently, the case of the type name MUST match. -# Built-in types are spelled in all-uppercase to match SQL convention. +# Note: currently, the case of the type name MUST match. +# Built-in types are spelled in all-uppercase to match SQL convention. 'UUID' = "crate::types::MyUuid" # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension) diff --git a/sqlx-core/src/config/tests.rs b/sqlx-core/src/config/tests.rs index 91e877afb6..d01b859e46 100644 --- a/sqlx-core/src/config/tests.rs +++ b/sqlx-core/src/config/tests.rs @@ -18,7 +18,16 @@ fn assert_common_config(config: &config::common::Config) { } fn assert_drivers_config(config: &config::drivers::Config) { - assert_eq!(config.sqlite.unsafe_load_extensions, ["uuid", "vsv"]); + assert_eq!( + config.sqlite.unsafe_load_extensions, + vec![ + config::drivers::SqliteExtension::Path("uuid".to_string()), + config::drivers::SqliteExtension::PathWithEntrypoint { + path: "vsv_renamed".to_string(), + entrypoint: "sqlite3_vsv_init".to_string() + } + ] + ); #[derive(Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all = "kebab-case")] diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index 8e58f4db5a..0cafc4d08c 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -511,7 +511,7 @@ impl SqliteConnectOptions { /// .extension("vsv") /// .extension("mod_spatialite"); /// } - /// + /// /// # Ok(options) /// # } /// ``` @@ -647,8 +647,13 @@ impl SqliteConnectOptions { for extension in &config.unsafe_load_extensions { // SAFETY: the documentation warns the user about loading extensions match extension { - config::drivers::SqliteExtension::Path(path) => self = unsafe { self.extension(path.clone()) }, - config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => self = unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) }, + config::drivers::SqliteExtension::Path(path) => { + self = unsafe { self.extension(path.clone()) } + } + config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => { + self = + unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) } + } } }