-
Notifications
You must be signed in to change notification settings - Fork 1.4k
SQLite extension loading via sqlx.toml for CLI and query macros #3713
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
base: sqlx-toml
Are you sure you want to change the base?
Changes from all commits
b5da0d7
f3d2d38
22d0d2c
0229e1e
ad9ca16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "sqlx-example-sqlite-extension" | ||
version = "0.1.0" | ||
license.workspace = true | ||
edition.workspace = true | ||
repository.workspace = true | ||
keywords.workspace = true | ||
categories.workspace = true | ||
authors.workspace = true | ||
|
||
[dependencies] | ||
sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } | ||
tokio = { version = "1.20.0", features = ["rt", "macros"]} | ||
anyhow = "1.0" | ||
|
||
[lints] | ||
workspace = true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash | ||
|
||
# This grabs a pre-compiled version of the extension used in this | ||
# example, and stores it in a temporary directory. That's a bit | ||
# unusual. Normally, any extensions you need will be installed into a | ||
# 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
create table addresses (address text, family integer); | ||
|
||
-- The `ipfamily` function is provided by the | ||
-- [ipaddr](https://github.com/nalgeon/sqlean/blob/main/docs/ipaddr.md) | ||
-- sqlite extension, and so this migration can not run if that | ||
-- extension is not loaded. | ||
insert into addresses (address, family) values | ||
('fd04:3d29:9f41::1', ipfamily('fd04:3d29:9f41::1')), | ||
('10.0.0.1', ipfamily('10.0.0.1')), | ||
('10.0.0.2', ipfamily('10.0.0.2')), | ||
('fd04:3d29:9f41::2', ipfamily('fd04:3d29:9f41::2')), | ||
('fd04:3d29:9f41::3', ipfamily('fd04:3d29:9f41::3')), | ||
('10.0.0.3', ipfamily('10.0.0.3')), | ||
('fd04:3d29:9f41::4', ipfamily('fd04:3d29:9f41::4')), | ||
('fd04:3d29:9f41::5', ipfamily('fd04:3d29:9f41::5')), | ||
('fd04:3d29:9f41::6', ipfamily('fd04:3d29:9f41::6')), | ||
('10.0.0.4', ipfamily('10.0.0.4')), | ||
('10.0.0.5', ipfamily('10.0.0.5')), | ||
('10.0.0.6', ipfamily('10.0.0.6')), | ||
('10.0.0.7', ipfamily('10.0.0.7')), | ||
('fd04:3d29:9f41::7', ipfamily('fd04:3d29:9f41::7')), | ||
('fd04:3d29:9f41::8', ipfamily('fd04:3d29:9f41::8')), | ||
('10.0.0.8', ipfamily('10.0.0.8')), | ||
('fd04:3d29:9f41::9', ipfamily('fd04:3d29:9f41::9')), | ||
('10.0.0.9', ipfamily('10.0.0.9')); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[common.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 | ||
# case here, the load-extensions value could just be `["ipaddr"]` | ||
# | ||
# When the extension file is installed in a non-standard location, as | ||
# in this example, there are two options: | ||
# * 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. | ||
load-extensions = ["/tmp/sqlite3-lib/ipaddr"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use std::str::FromStr; | ||
|
||
use sqlx::{query, sqlite::{SqlitePool, SqliteConnectOptions}}; | ||
|
||
#[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 db = SqlitePool::connect_with(opts).await?; | ||
|
||
query!("insert into addresses (address, family) values (?1, ipfamily(?1))", "10.0.0.10").execute(&db).await?; | ||
|
||
println!("Query which requires the extension was successfully executed."); | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ use url::Url; | |
pub struct AnyConnectOptions { | ||
pub database_url: Url, | ||
pub log_settings: LogSettings, | ||
pub enable_config: bool, | ||
Comment on lines
20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that these are part of the public interface, they should probably all have doc comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably, but not having a doc for enable_config is consistent with the surrounding code. Documenting all of the options would be a subject for a different pull request. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I'd say that documenting new code is more important than being consistent with undocumented items. enable_config is effectively meaningless as a field name without additional context. Obviously we have it right now, but the docs are for people that come later. |
||
} | ||
impl FromStr for AnyConnectOptions { | ||
type Err = Error; | ||
|
@@ -29,6 +30,7 @@ impl FromStr for AnyConnectOptions { | |
.parse::<Url>() | ||
.map_err(|e| Error::Configuration(e.into()))?, | ||
log_settings: LogSettings::default(), | ||
enable_config: false, | ||
}) | ||
} | ||
} | ||
|
@@ -40,6 +42,7 @@ impl ConnectOptions for AnyConnectOptions { | |
Ok(AnyConnectOptions { | ||
database_url: url.clone(), | ||
log_settings: LogSettings::default(), | ||
enable_config: false, | ||
}) | ||
} | ||
|
||
|
@@ -63,3 +66,15 @@ impl ConnectOptions for AnyConnectOptions { | |
self | ||
} | ||
} | ||
|
||
impl AnyConnectOptions { | ||
/// UNSTABLE: for use with `sqlx-cli` | ||
/// | ||
/// Allow nested drivers to extract configuration information from | ||
/// the sqlx.toml file. | ||
#[doc(hidden)] | ||
pub fn allow_config_file(mut self) -> Self { | ||
self.enable_config = true; | ||
self | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,10 +40,49 @@ pub struct Config { | |
/// The query macros used in `foo` will use `FOO_DATABASE_URL`, | ||
/// and the ones used in `bar` will use `BAR_DATABASE_URL`. | ||
pub database_url_var: Option<String>, | ||
|
||
/// Settings for specific database drivers. | ||
/// | ||
/// These settings apply when checking queries, or when applying | ||
/// migrations via `sqlx-cli`. These settings *do not* apply when | ||
/// applying migrations via the macro, as that uses the run-time | ||
/// database connection configured by the application. | ||
pub drivers: Drivers, | ||
} | ||
|
||
impl Config { | ||
pub fn database_url_var(&self) -> &str { | ||
self.database_url_var.as_deref().unwrap_or("DATABASE_URL") | ||
} | ||
} | ||
|
||
/// Configuration for specific database drivers. | ||
#[derive(Debug, Default)] | ||
#[cfg_attr( | ||
feature = "sqlx-toml", | ||
derive(serde::Deserialize), | ||
serde(default, rename_all = "kebab-case") | ||
)] | ||
pub struct Drivers { | ||
/// Specify options for the SQLite driver. | ||
pub sqlite: SQLite, | ||
} | ||
|
||
/// Configuration for the SQLite database driver. | ||
#[derive(Debug, Default)] | ||
#[cfg_attr( | ||
feature = "sqlx-toml", | ||
derive(serde::Deserialize), | ||
serde(default, rename_all = "kebab-case") | ||
)] | ||
pub struct SQLite { | ||
/// Specify extensions to load. | ||
/// | ||
Comment on lines
+79
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth adding docs about the file path / LD_LIBRARY_PATH here similar to above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems redundant to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason that it's not (IMO) is the docs page is where you would generally discover information about how to configure this structure. The reference toml file may never be seen or even thought of as a possible place where there's documentation about this by someone looking for config information. |
||
/// # Example: Load the "uuid" and "vsv" extensions | ||
/// `sqlx.toml`: | ||
/// ```toml | ||
/// [common.drivers.sqlite] | ||
/// load-extensions = ["uuid", "vsv"] | ||
/// ``` | ||
pub load_extensions: Vec<String>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be reasonable to make this available to apps using sqlx other than just the cli? What makes it a sqlx-cli only option? Would it make sense to instead make loading the config file default functionality (or perhaps feature gated instead of adding a new method)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, but I was working on the principle of least change. The CLI is the only place I needed it. It would not be tagged as pub if that weren't a technical necessity, so marking it unstable and hiding it in the docs is the next best option.