Skip to content

Commit 3dd86ef

Browse files
committed
breaking(sqlite): libsqlite3-sys versioning, feature flags, extension loading unsafe
# Changes to unreleased code (fold together with related PRs) * Rename config key `common.drivers.sqlite.load-extensions` to `drivers.sqlite.unsafe-load-extensions` * Added `drivers.mysql`, `drivers.postgres`, and support for future external drivers # Breaking Changes * Changed `libsqlite3-sys` versioning policy to use version ranges * Diesel has been using this successfully for many years: https://github.com/diesel-rs/diesel/blob/cdf7c51c35da7ebc6fb12e71857bfba8487c20cd/diesel/Cargo.toml#L53 * Mark `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()` as `unsafe` * Added new non-default features corresponding to conditionally compiled SQLite APIs: * `sqlite-deserialize` enabling `SqliteConnection::serialize()` and `SqliteConnection::deserialize()` * `sqlite-load-extension` enabling `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()` * `sqlite-unlock-notify` enables internal use of `sqlite3_unlock_notify()` # TODO - [x] `sqlite3_value` is not safe to access concurrently - [ ] list all the issues this fixes
1 parent f7ef1ed commit 3dd86ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1054
-550
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ authors.workspace = true
5454
repository.workspace = true
5555
rust-version.workspace = true
5656

57+
# Note: written so that it may be copy-pasted to other crates
5758
[package.metadata.docs.rs]
58-
features = ["all-databases", "_unstable-all-types", "_unstable-doc", "sqlite-preupdate-hook"]
59+
features = ["_unstable-docs"]
5960
rustdoc-args = ["--cfg", "docsrs"]
6061

6162
[features]
@@ -83,8 +84,13 @@ _unstable-all-types = [
8384
"bit-vec",
8485
"bstr"
8586
]
87+
8688
# Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`).
87-
_unstable-doc = []
89+
_unstable-docs = [
90+
"all-databases",
91+
"_unstable-all-types",
92+
"sqlx-sqlite/_unstable-docs"
93+
]
8894

8995
# Base runtime features without TLS
9096
runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"]
@@ -110,10 +116,32 @@ _sqlite = []
110116
any = ["sqlx-core/any", "sqlx-mysql?/any", "sqlx-postgres?/any", "sqlx-sqlite?/any"]
111117
postgres = ["sqlx-postgres", "sqlx-macros?/postgres"]
112118
mysql = ["sqlx-mysql", "sqlx-macros?/mysql"]
113-
sqlite = ["_sqlite", "sqlx-sqlite/bundled", "sqlx-macros?/sqlite"]
119+
sqlite = ["sqlite-bundled", "sqlite-deserialize", "sqlite-load-extension", "sqlite-unlock-notify"]
120+
121+
# SQLite base features
122+
sqlite-bundled = ["_sqlite", "sqlx-sqlite/bundled", "sqlx-macros?/sqlite"]
114123
sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled", "sqlx-macros?/sqlite-unbundled"]
124+
125+
# SQLite features using conditionally compiled APIs
126+
# Note: these assume `sqlite-bundled` or `sqlite-unbundled` is also enabled
127+
#
128+
# Enable `SqliteConnection::deserialize()` and `::serialize()`
129+
# Cannot be used with `-DSQLITE_OMIT_DESERIALIZE`; requires `-DSQLITE_ENABLE_DESERIALIZE` on SQLite < 3.36.0
130+
sqlite-deserialize = ["sqlx-sqlite/deserialize"]
131+
132+
# Enable `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()`.
133+
# Also required to use `drivers.sqlite.unsafe-load-extensions` from `sqlx.toml`.
134+
# Cannot be used with `-DSQLITE_OMIT_LOAD_EXTENSION`
135+
sqlite-load-extension = ["sqlx-sqlite/load-extension", "sqlx-macros?/sqlite-load-extension"]
136+
137+
# Enables `sqlite3_preupdate_hook`
138+
# Requires `-DSQLITE_ENABLE_PREUPDATE_HOOK` (set automatically with `sqlite-bundled`)
115139
sqlite-preupdate-hook = ["sqlx-sqlite/preupdate-hook"]
116140

141+
# Enable internal handling of `SQLITE_LOCKED_SHAREDCACHE`
142+
# Requires `-DSQLITE_ENABLE_UNLOCK_NOTIFY` (set automatically with `sqlite-bundled`)
143+
sqlite-unlock-notify = ["sqlx-sqlite/unlock-notify"]
144+
117145
# types
118146
json = ["sqlx-core/json", "sqlx-macros?/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"]
119147

@@ -211,8 +239,14 @@ cast_sign_loss = 'deny'
211239
# See `clippy.toml`
212240
disallowed_methods = 'deny'
213241

214-
[lints.rust]
215-
unexpected_cfgs = { level = 'warn', check-cfg = ['cfg(mariadb, values(any()))'] }
242+
243+
[lints.rust.unexpected_cfgs]
244+
level = 'warn'
245+
check-cfg = [
246+
'cfg(mariadb, values(any()))',
247+
'cfg(sqlite_ipaddr)',
248+
'cfg(sqlite_test_sqlcipher)',
249+
]
216250

217251
#
218252
# Any

examples/sqlite/extension/sqlx.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
# Including the full path to the extension is somewhat unusual,
33
# because normally an extension will be installed in a standard
44
# directory which is part of the library search path. If that were the
5-
# case here, the load-extensions value could just be `["ipaddr"]`
5+
# case here, the unsafe-load-extensions value could just be `["ipaddr"]`
66
#
77
# When the extension file is installed in a non-standard location, as
88
# in this example, there are two options:
99
# * Provide the full path the the extension, as seen below.
1010
# * Add the non-standard location to the library search path, which on
1111
# Linux means adding it to the LD_LIBRARY_PATH environment variable.
12-
load-extensions = ["/tmp/sqlite3-lib/ipaddr"]
12+
unsafe-load-extensions = ["/tmp/sqlite3-lib/ipaddr"]

sqlx-cli/src/lib.rs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ async fn do_run(opt: Opt) -> anyhow::Result<()> {
177177
} => {
178178
let config = config.load_config().await?;
179179
connect_opts.populate_db_url(&config)?;
180-
prepare::run(check, all, workspace, connect_opts, args).await?
180+
prepare::run(&config, check, all, workspace, connect_opts, args).await?
181181
}
182182

183183
#[cfg(feature = "completions")]
@@ -188,27 +188,9 @@ async fn do_run(opt: Opt) -> anyhow::Result<()> {
188188
}
189189

190190
/// Attempt to connect to the database server, retrying up to `ops.connect_timeout`.
191-
async fn connect(opts: &ConnectOpts) -> anyhow::Result<AnyConnection> {
191+
async fn connect(config: &Config, opts: &ConnectOpts) -> anyhow::Result<AnyConnection> {
192192
retry_connect_errors(opts, move |url| {
193-
// This only handles the default case. For good support of
194-
// the new command line options, we need to work out some
195-
// way to make the appropriate ConfigOpt available here. I
196-
// suspect that that infrastructure would be useful for
197-
// other things in the future, as well, but it also seems
198-
// like an extensive and intrusive change.
199-
//
200-
// On the other hand, the compile-time checking macros
201-
// can't be configured to use a different config file at
202-
// all, so I believe this is okay for the time being.
203-
let config = Some(std::path::PathBuf::from("sqlx.toml")).and_then(|p| {
204-
if p.exists() {
205-
Some(p)
206-
} else {
207-
None
208-
}
209-
});
210-
211-
async move { AnyConnection::connect_with_config(url, config.clone()).await }
193+
AnyConnection::connect_with_driver_config(url, &config.drivers)
212194
})
213195
.await
214196
}

sqlx-cli/src/migrate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub async fn info(
130130
) -> anyhow::Result<()> {
131131
let migrator = migration_source.resolve(config).await?;
132132

133-
let mut conn = crate::connect(connect_opts).await?;
133+
let mut conn = crate::connect(config, connect_opts).await?;
134134

135135
// FIXME: we shouldn't actually be creating anything here
136136
for schema_name in &config.migrate.create_schemas {
@@ -229,7 +229,7 @@ pub async fn run(
229229
}
230230
}
231231

232-
let mut conn = crate::connect(connect_opts).await?;
232+
let mut conn = crate::connect(config, connect_opts).await?;
233233

234234
for schema_name in &config.migrate.create_schemas {
235235
conn.create_schema_if_not_exists(schema_name).await?;
@@ -331,7 +331,7 @@ pub async fn revert(
331331
}
332332
}
333333

334-
let mut conn = crate::connect(connect_opts).await?;
334+
let mut conn = crate::connect(config, connect_opts).await?;
335335

336336
// FIXME: we should not be creating anything here if it doesn't exist
337337
for schema_name in &config.migrate.create_schemas {

sqlx-cli/src/prepare.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ use std::fs;
55
use std::path::{Path, PathBuf};
66
use std::process::Command;
77

8+
use crate::metadata::{manifest_dir, Metadata};
9+
use crate::opt::ConnectOpts;
10+
use crate::Config;
811
use anyhow::{bail, Context};
912
use console::style;
1013
use sqlx::Connection;
1114

12-
use crate::metadata::{manifest_dir, Metadata};
13-
use crate::opt::ConnectOpts;
14-
15-
pub struct PrepareCtx {
15+
pub struct PrepareCtx<'a> {
16+
pub config: &'a Config,
1617
pub workspace: bool,
1718
pub all: bool,
1819
pub cargo: OsString,
@@ -21,7 +22,7 @@ pub struct PrepareCtx {
2122
pub connect_opts: ConnectOpts,
2223
}
2324

24-
impl PrepareCtx {
25+
impl PrepareCtx<'_> {
2526
/// Path to the directory where cached queries should be placed.
2627
fn prepare_dir(&self) -> anyhow::Result<PathBuf> {
2728
if self.workspace {
@@ -33,6 +34,7 @@ impl PrepareCtx {
3334
}
3435

3536
pub async fn run(
37+
config: &Config,
3638
check: bool,
3739
all: bool,
3840
workspace: bool,
@@ -50,6 +52,7 @@ hint: This command only works in the manifest directory of a Cargo package or wo
5052

5153
let metadata: Metadata = Metadata::from_current_directory(&cargo)?;
5254
let ctx = PrepareCtx {
55+
config,
5356
workspace,
5457
all,
5558
cargo,
@@ -65,9 +68,9 @@ hint: This command only works in the manifest directory of a Cargo package or wo
6568
}
6669
}
6770

68-
async fn prepare(ctx: &PrepareCtx) -> anyhow::Result<()> {
71+
async fn prepare(ctx: &PrepareCtx<'_>) -> anyhow::Result<()> {
6972
if ctx.connect_opts.database_url.is_some() {
70-
check_backend(&ctx.connect_opts).await?;
73+
check_backend(ctx.config, &ctx.connect_opts).await?;
7174
}
7275

7376
let prepare_dir = ctx.prepare_dir()?;
@@ -93,9 +96,9 @@ async fn prepare(ctx: &PrepareCtx) -> anyhow::Result<()> {
9396
Ok(())
9497
}
9598

96-
async fn prepare_check(ctx: &PrepareCtx) -> anyhow::Result<()> {
99+
async fn prepare_check(ctx: &PrepareCtx<'_>) -> anyhow::Result<()> {
97100
if ctx.connect_opts.database_url.is_some() {
98-
check_backend(&ctx.connect_opts).await?;
101+
check_backend(ctx.config, &ctx.connect_opts).await?;
99102
}
100103

101104
// Re-generate and store the queries in a separate directory from both the prepared
@@ -359,8 +362,8 @@ fn load_json_file(path: impl AsRef<Path>) -> anyhow::Result<serde_json::Value> {
359362
Ok(serde_json::from_slice(&file_bytes)?)
360363
}
361364

362-
async fn check_backend(opts: &ConnectOpts) -> anyhow::Result<()> {
363-
crate::connect(opts).await?.close().await?;
365+
async fn check_backend(config: &Config, opts: &ConnectOpts) -> anyhow::Result<()> {
366+
crate::connect(config, opts).await?.close().await?;
364367
Ok(())
365368
}
366369

sqlx-core/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ futures-util = { version = "0.3.19", default-features = false, features = ["allo
7676
log = { version = "0.4.18", default-features = false }
7777
memchr = { version = "2.4.1", default-features = false }
7878
percent-encoding = "2.1.0"
79-
regex = { version = "1.5.5", optional = true }
8079
serde = { version = "1.0.132", features = ["derive", "rc"], optional = true }
8180
serde_json = { version = "1.0.73", features = ["raw_value"], optional = true }
8281
toml = { version = "0.8.16", optional = true }

sqlx-core/src/any/connection/mod.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use crate::any::{Any, AnyConnectOptions};
55
use crate::connection::{ConnectOptions, Connection};
66
use crate::error::Error;
77

8+
use crate::config;
89
use crate::database::Database;
910
use crate::sql_str::SqlSafeStr;
10-
pub use backend::AnyConnectionBackend;
11-
1211
use crate::transaction::Transaction;
12+
pub use backend::AnyConnectionBackend;
1313

1414
mod backend;
1515
mod executor;
@@ -37,40 +37,45 @@ impl AnyConnection {
3737
pub(crate) fn connect(options: &AnyConnectOptions) -> BoxFuture<'_, crate::Result<Self>> {
3838
Box::pin(async {
3939
let driver = crate::any::driver::from_url(&options.database_url)?;
40-
(driver.connect)(options).await
40+
(*driver.connect)(options, None).await
4141
})
4242
}
4343

4444
/// UNSTABLE: for use with `sqlx-cli`
4545
///
4646
/// Connect to the database, and instruct the nested driver to
4747
/// read options from the sqlx.toml file as appropriate.
48-
#[cfg(feature = "sqlx-toml")]
4948
#[doc(hidden)]
50-
pub fn connect_with_config(
49+
pub async fn connect_with_driver_config(
5150
url: &str,
52-
path: Option<std::path::PathBuf>,
53-
) -> BoxFuture<'static, Result<Self, Error>>
51+
driver_config: &config::drivers::Config,
52+
) -> Result<Self, Error>
5453
where
5554
Self: Sized,
5655
{
57-
let options: Result<AnyConnectOptions, Error> = url.parse();
56+
let options: AnyConnectOptions = url.parse()?;
5857

59-
Box::pin(async move { Self::connect_with(&options?.with_config_file(path)).await })
58+
let driver = crate::any::driver::from_url(&options.database_url)?;
59+
(*driver.connect)(&options, Some(driver_config)).await
6060
}
6161

62-
pub(crate) fn connect_with_db<DB: Database>(
63-
options: &AnyConnectOptions,
64-
) -> BoxFuture<'_, crate::Result<Self>>
62+
pub(crate) fn connect_with_db<'a, DB: Database>(
63+
options: &'a AnyConnectOptions,
64+
driver_config: Option<&'a config::drivers::Config>,
65+
) -> BoxFuture<'a, crate::Result<Self>>
6566
where
6667
DB::Connection: AnyConnectionBackend,
6768
<DB::Connection as Connection>::Options:
68-
for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>,
69+
for<'b> TryFrom<&'b AnyConnectOptions, Error = Error>,
6970
{
7071
let res = TryFrom::try_from(options);
7172

72-
Box::pin(async {
73-
let options: <DB::Connection as Connection>::Options = res?;
73+
Box::pin(async move {
74+
let mut options: <DB::Connection as Connection>::Options = res?;
75+
76+
if let Some(config) = driver_config {
77+
options = options.__unstable_apply_driver_config(config)?;
78+
}
7479

7580
Ok(AnyConnection {
7681
backend: Box::new(options.connect().await?),

sqlx-core/src/any/driver.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ use crate::any::{AnyConnectOptions, AnyConnection};
33
use crate::common::DebugFn;
44
use crate::connection::Connection;
55
use crate::database::Database;
6-
use crate::Error;
6+
use crate::{config, Error};
77
use futures_core::future::BoxFuture;
8-
use futures_util::FutureExt;
98
use std::fmt::{Debug, Formatter};
109
use std::sync::OnceLock;
1110
use url::Url;
@@ -29,8 +28,12 @@ macro_rules! declare_driver_with_optional_migrate {
2928
pub struct AnyDriver {
3029
pub(crate) name: &'static str,
3130
pub(crate) url_schemes: &'static [&'static str],
32-
pub(crate) connect:
33-
DebugFn<fn(&AnyConnectOptions) -> BoxFuture<'_, crate::Result<AnyConnection>>>,
31+
pub(crate) connect: DebugFn<
32+
for<'a> fn(
33+
&'a AnyConnectOptions,
34+
Option<&'a config::drivers::Config>,
35+
) -> BoxFuture<'a, crate::Result<AnyConnection>>,
36+
>,
3437
pub(crate) migrate_database: Option<AnyMigrateDatabase>,
3538
}
3639

@@ -68,10 +71,10 @@ impl AnyDriver {
6871
{
6972
Self {
7073
migrate_database: Some(AnyMigrateDatabase {
71-
create_database: DebugFn(|url| DB::create_database(url).boxed()),
72-
database_exists: DebugFn(|url| DB::database_exists(url).boxed()),
73-
drop_database: DebugFn(|url| DB::drop_database(url).boxed()),
74-
force_drop_database: DebugFn(|url| DB::force_drop_database(url).boxed()),
74+
create_database: DebugFn(|url| Box::pin(DB::create_database(url))),
75+
database_exists: DebugFn(|url| Box::pin(DB::database_exists(url))),
76+
drop_database: DebugFn(|url| Box::pin(DB::drop_database(url))),
77+
force_drop_database: DebugFn(|url| Box::pin(DB::force_drop_database(url))),
7578
}),
7679
..Self::without_migrate::<DB>()
7780
}
@@ -131,6 +134,7 @@ pub fn install_drivers(
131134
.map_err(|_| "drivers already installed".into())
132135
}
133136

137+
#[cfg(feature = "migrate")]
134138
pub(crate) fn from_url_str(url: &str) -> crate::Result<&'static AnyDriver> {
135139
from_url(&url.parse().map_err(Error::config)?)
136140
}

0 commit comments

Comments
 (0)