Skip to content
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
6 changes: 5 additions & 1 deletion examples/sqlite/extension/download-extension.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions examples/sqlite/extension/migrations/20251115215857_uuid.sql
Original file line number Diff line number Diff line change
@@ -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());
7 changes: 5 additions & 2 deletions examples/sqlite/extension/sqlx.toml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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" },
]
50 changes: 31 additions & 19 deletions examples/sqlite/extension/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand All @@ -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(())
}
14 changes: 13 additions & 1 deletion sqlx-core/src/config/drivers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,19 @@ pub struct SqliteConfig {
/// [common.drivers.sqlite]
/// unsafe-load-extensions = ["uuid", "vsv"]
/// ```
pub unsafe_load_extensions: Vec<String>,
pub unsafe_load_extensions: Vec<SqliteExtension>,
}

/// Extension for the SQLite database driver.
#[derive(Debug, PartialEq)]
#[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.
Expand Down
9 changes: 6 additions & 3 deletions sqlx-core/src/config/reference.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion sqlx-core/src/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
12 changes: 10 additions & 2 deletions sqlx-sqlite/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ impl SqliteConnectOptions {
/// .extension("vsv")
/// .extension("mod_spatialite");
/// }
///
///
/// # Ok(options)
/// # }
/// ```
Expand Down Expand Up @@ -646,7 +646,15 @@ 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"))]
Expand Down
Loading