From 3b1d389556375d093f38ea38a4a4ec3d696afaf5 Mon Sep 17 00:00:00 2001 From: Lining Pan Date: Mon, 1 Aug 2022 13:07:38 -0400 Subject: [PATCH 1/3] feat: allow vfs to be set as uri query parameter --- sqlx-core/src/sqlite/connection/establish.rs | 12 ++- sqlx-core/src/sqlite/options/mod.rs | 4 + sqlx-core/src/sqlite/options/parse.rs | 51 ++++++++++++ sqlx-core/src/sqlite/options/vfs.rs | 85 ++++++++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 sqlx-core/src/sqlite/options/vfs.rs diff --git a/sqlx-core/src/sqlite/connection/establish.rs b/sqlx-core/src/sqlite/connection/establish.rs index ae093fada5..ceb3e8c89b 100644 --- a/sqlx-core/src/sqlite/connection/establish.rs +++ b/sqlx-core/src/sqlite/connection/establish.rs @@ -67,8 +67,18 @@ impl EstablishParams { SQLITE_OPEN_PRIVATECACHE }; + let mut query_params: Vec = vec![]; + if options.immutable { - filename = format!("file:{}?immutable=true", filename); + query_params.push("immutable=true".into()) + } + + if let Some(vfs) = options.vfs { + query_params.push(format!("vfs={}", vfs.as_str())) + } + + if !query_params.is_empty() { + filename = format!("file:{}?{}", filename, query_params.join("&")); flags |= libsqlite3_sys::SQLITE_OPEN_URI; } diff --git a/sqlx-core/src/sqlite/options/mod.rs b/sqlx-core/src/sqlite/options/mod.rs index b3bb53e5a4..b056bc3bc1 100644 --- a/sqlx-core/src/sqlite/options/mod.rs +++ b/sqlx-core/src/sqlite/options/mod.rs @@ -6,6 +6,7 @@ mod journal_mode; mod locking_mode; mod parse; mod synchronous; +mod vfs; use crate::connection::LogSettings; pub use auto_vacuum::SqliteAutoVacuum; @@ -15,6 +16,7 @@ use std::cmp::Ordering; use std::sync::Arc; use std::{borrow::Cow, time::Duration}; pub use synchronous::SqliteSynchronous; +pub use vfs::SqliteVfs; use crate::common::DebugFn; use crate::sqlite::connection::collation::Collation; @@ -63,6 +65,7 @@ pub struct SqliteConnectOptions { pub(crate) busy_timeout: Duration, pub(crate) log_settings: LogSettings, pub(crate) immutable: bool, + pub(crate) vfs: Option, pub(crate) pragmas: IndexMap, Option>>, @@ -135,6 +138,7 @@ impl SqliteConnectOptions { busy_timeout: Duration::from_secs(5), log_settings: Default::default(), immutable: false, + vfs: None, pragmas, collations: Default::default(), serialized: false, diff --git a/sqlx-core/src/sqlite/options/parse.rs b/sqlx-core/src/sqlite/options/parse.rs index 7f80dfd7d0..5595e56222 100644 --- a/sqlx-core/src/sqlite/options/parse.rs +++ b/sqlx-core/src/sqlite/options/parse.rs @@ -1,3 +1,4 @@ +use super::SqliteVfs; use crate::error::Error; use crate::sqlite::SqliteConnectOptions; use percent_encoding::percent_decode_str; @@ -108,6 +109,8 @@ impl FromStr for SqliteConnectOptions { } }, + "vfs" => options.vfs = Some(SqliteVfs::from_str(&*value)?), + _ => { return Err(Error::Configuration( format!( @@ -163,3 +166,51 @@ fn test_parse_shared_in_memory() -> Result<(), Error> { Ok(()) } + +#[test] +#[cfg(target_family = "unix")] +fn test_parse_vfs() -> Result<(), Error> { + let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::Unix)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-dotfile".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::UnixDotfile)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-excl".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::UnixExcl)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-none".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::UnixNone)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-namedsem".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::UnixNamedsem)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + Ok(()) +} + +#[test] +#[cfg(target_family = "windows")] +fn test_parse_vfs() -> Result<(), Error> { + let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::Win32)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-longpath".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::Win32Longpath)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-none".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::Win32None)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-longpath-none".parse()?; + assert_eq!(options.vfs, Some(SqliteVfs::Win32LongpathNone)); + assert_eq!(&*options.filename.to_string_lossy(), "a.db"); + + Ok(()) +} diff --git a/sqlx-core/src/sqlite/options/vfs.rs b/sqlx-core/src/sqlite/options/vfs.rs new file mode 100644 index 0000000000..beb30574b7 --- /dev/null +++ b/sqlx-core/src/sqlite/options/vfs.rs @@ -0,0 +1,85 @@ +use crate::error::Error; +use std::str::FromStr; + +/// Refer to [SQLite documentation] for available VFSes. +/// Currently only standard VFSes are supported +/// +/// [SQLite documentation]: https://www.sqlite.org/vfs.html +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SqliteVfs { + #[cfg(target_family = "unix")] + Unix, + #[cfg(target_family = "unix")] + UnixDotfile, + #[cfg(target_family = "unix")] + UnixExcl, + #[cfg(target_family = "unix")] + UnixNone, + #[cfg(target_family = "unix")] + UnixNamedsem, + #[cfg(target_family = "windows")] + Win32, + #[cfg(target_family = "windows")] + Win32Longpath, + #[cfg(target_family = "windows")] + Win32None, + #[cfg(target_family = "windows")] + Win32LongpathNone, +} + +impl SqliteVfs { + pub(crate) fn as_str(&self) -> &'static str { + match self { + #[cfg(target_family = "unix")] + SqliteVfs::Unix => "unix", + #[cfg(target_family = "unix")] + SqliteVfs::UnixDotfile => "unix-dotfile", + #[cfg(target_family = "unix")] + SqliteVfs::UnixExcl => "unix-excl", + #[cfg(target_family = "unix")] + SqliteVfs::UnixNone => "unix-none", + #[cfg(target_family = "unix")] + SqliteVfs::UnixNamedsem => "unix-namedsem", + #[cfg(target_family = "windows")] + SqliteVfs::Win32 => "win32", + #[cfg(target_family = "windows")] + SqliteVfs::Win32Longpath => "win32-longpath", + #[cfg(target_family = "windows")] + SqliteVfs::Win32None => "win32-none", + #[cfg(target_family = "windows")] + SqliteVfs::Win32LongpathNone => "win32-longpath-none", + } + } +} + +impl FromStr for SqliteVfs { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match &*s.to_ascii_lowercase() { + #[cfg(target_family = "unix")] + "unix" => SqliteVfs::Unix, + #[cfg(target_family = "unix")] + "unix-dotfile" => SqliteVfs::UnixDotfile, + #[cfg(target_family = "unix")] + "unix-excl" => SqliteVfs::UnixExcl, + #[cfg(target_family = "unix")] + "unix-none" => SqliteVfs::UnixNone, + #[cfg(target_family = "unix")] + "unix-namedsem" => SqliteVfs::UnixNamedsem, + #[cfg(target_family = "windows")] + "win32" => SqliteVfs::Win32, + #[cfg(target_family = "windows")] + "win32-longpath" => SqliteVfs::Win32Longpath, + #[cfg(target_family = "windows")] + "win32-none" => SqliteVfs::Win32None, + #[cfg(target_family = "windows")] + "win32-longpath-none" => SqliteVfs::Win32LongpathNone, + _ => { + return Err(Error::Configuration( + format!("unknown value {:?} for `vfs`", s).into(), + )); + } + }) + } +} From 1d79ae7a04b27bf27b4f172f79baeffd08afe580 Mon Sep 17 00:00:00 2001 From: Lining Pan Date: Tue, 2 Aug 2022 09:18:28 -0400 Subject: [PATCH 2/3] fix: handle VFS name as a string --- sqlx-core/src/sqlite/connection/establish.rs | 4 +- sqlx-core/src/sqlite/options/mod.rs | 13 ++- sqlx-core/src/sqlite/options/parse.rs | 51 +----------- sqlx-core/src/sqlite/options/vfs.rs | 85 -------------------- 4 files changed, 13 insertions(+), 140 deletions(-) delete mode 100644 sqlx-core/src/sqlite/options/vfs.rs diff --git a/sqlx-core/src/sqlite/connection/establish.rs b/sqlx-core/src/sqlite/connection/establish.rs index ceb3e8c89b..ead59a9dfa 100644 --- a/sqlx-core/src/sqlite/connection/establish.rs +++ b/sqlx-core/src/sqlite/connection/establish.rs @@ -73,8 +73,8 @@ impl EstablishParams { query_params.push("immutable=true".into()) } - if let Some(vfs) = options.vfs { - query_params.push(format!("vfs={}", vfs.as_str())) + if let Some(vfs) = &options.vfs { + query_params.push(format!("vfs={}", vfs)) } if !query_params.is_empty() { diff --git a/sqlx-core/src/sqlite/options/mod.rs b/sqlx-core/src/sqlite/options/mod.rs index b056bc3bc1..6c1813ae86 100644 --- a/sqlx-core/src/sqlite/options/mod.rs +++ b/sqlx-core/src/sqlite/options/mod.rs @@ -6,7 +6,6 @@ mod journal_mode; mod locking_mode; mod parse; mod synchronous; -mod vfs; use crate::connection::LogSettings; pub use auto_vacuum::SqliteAutoVacuum; @@ -16,7 +15,6 @@ use std::cmp::Ordering; use std::sync::Arc; use std::{borrow::Cow, time::Duration}; pub use synchronous::SqliteSynchronous; -pub use vfs::SqliteVfs; use crate::common::DebugFn; use crate::sqlite::connection::collation::Collation; @@ -65,7 +63,7 @@ pub struct SqliteConnectOptions { pub(crate) busy_timeout: Duration, pub(crate) log_settings: LogSettings, pub(crate) immutable: bool, - pub(crate) vfs: Option, + pub(crate) vfs: Option>, pub(crate) pragmas: IndexMap, Option>>, @@ -371,4 +369,13 @@ impl SqliteConnectOptions { self.row_channel_size = size; self } + + /// Sets the [`vfs`](https://www.sqlite.org/vfs.html) parameter of the database connection. + /// + /// The default value is empty, and sqlite will use the default VFS object dependeing on the + /// operating system. + pub fn vfs(mut self, vfs_name: impl AsRef) -> Self { + self.vfs = Some(Cow::Owned(vfs_name.as_ref().to_string())); + self + } } diff --git a/sqlx-core/src/sqlite/options/parse.rs b/sqlx-core/src/sqlite/options/parse.rs index 5595e56222..94cf0cd397 100644 --- a/sqlx-core/src/sqlite/options/parse.rs +++ b/sqlx-core/src/sqlite/options/parse.rs @@ -1,4 +1,3 @@ -use super::SqliteVfs; use crate::error::Error; use crate::sqlite::SqliteConnectOptions; use percent_encoding::percent_decode_str; @@ -109,7 +108,7 @@ impl FromStr for SqliteConnectOptions { } }, - "vfs" => options.vfs = Some(SqliteVfs::from_str(&*value)?), + "vfs" => options.vfs = Some(Cow::Owned(value.into_owned())), _ => { return Err(Error::Configuration( @@ -166,51 +165,3 @@ fn test_parse_shared_in_memory() -> Result<(), Error> { Ok(()) } - -#[test] -#[cfg(target_family = "unix")] -fn test_parse_vfs() -> Result<(), Error> { - let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::Unix)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-dotfile".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::UnixDotfile)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-excl".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::UnixExcl)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-none".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::UnixNone)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=unix-namedsem".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::UnixNamedsem)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - Ok(()) -} - -#[test] -#[cfg(target_family = "windows")] -fn test_parse_vfs() -> Result<(), Error> { - let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::Win32)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-longpath".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::Win32Longpath)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-none".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::Win32None)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - let options: SqliteConnectOptions = "sqlite://a.db?vfs=win32-longpath-none".parse()?; - assert_eq!(options.vfs, Some(SqliteVfs::Win32LongpathNone)); - assert_eq!(&*options.filename.to_string_lossy(), "a.db"); - - Ok(()) -} diff --git a/sqlx-core/src/sqlite/options/vfs.rs b/sqlx-core/src/sqlite/options/vfs.rs deleted file mode 100644 index beb30574b7..0000000000 --- a/sqlx-core/src/sqlite/options/vfs.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::error::Error; -use std::str::FromStr; - -/// Refer to [SQLite documentation] for available VFSes. -/// Currently only standard VFSes are supported -/// -/// [SQLite documentation]: https://www.sqlite.org/vfs.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SqliteVfs { - #[cfg(target_family = "unix")] - Unix, - #[cfg(target_family = "unix")] - UnixDotfile, - #[cfg(target_family = "unix")] - UnixExcl, - #[cfg(target_family = "unix")] - UnixNone, - #[cfg(target_family = "unix")] - UnixNamedsem, - #[cfg(target_family = "windows")] - Win32, - #[cfg(target_family = "windows")] - Win32Longpath, - #[cfg(target_family = "windows")] - Win32None, - #[cfg(target_family = "windows")] - Win32LongpathNone, -} - -impl SqliteVfs { - pub(crate) fn as_str(&self) -> &'static str { - match self { - #[cfg(target_family = "unix")] - SqliteVfs::Unix => "unix", - #[cfg(target_family = "unix")] - SqliteVfs::UnixDotfile => "unix-dotfile", - #[cfg(target_family = "unix")] - SqliteVfs::UnixExcl => "unix-excl", - #[cfg(target_family = "unix")] - SqliteVfs::UnixNone => "unix-none", - #[cfg(target_family = "unix")] - SqliteVfs::UnixNamedsem => "unix-namedsem", - #[cfg(target_family = "windows")] - SqliteVfs::Win32 => "win32", - #[cfg(target_family = "windows")] - SqliteVfs::Win32Longpath => "win32-longpath", - #[cfg(target_family = "windows")] - SqliteVfs::Win32None => "win32-none", - #[cfg(target_family = "windows")] - SqliteVfs::Win32LongpathNone => "win32-longpath-none", - } - } -} - -impl FromStr for SqliteVfs { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match &*s.to_ascii_lowercase() { - #[cfg(target_family = "unix")] - "unix" => SqliteVfs::Unix, - #[cfg(target_family = "unix")] - "unix-dotfile" => SqliteVfs::UnixDotfile, - #[cfg(target_family = "unix")] - "unix-excl" => SqliteVfs::UnixExcl, - #[cfg(target_family = "unix")] - "unix-none" => SqliteVfs::UnixNone, - #[cfg(target_family = "unix")] - "unix-namedsem" => SqliteVfs::UnixNamedsem, - #[cfg(target_family = "windows")] - "win32" => SqliteVfs::Win32, - #[cfg(target_family = "windows")] - "win32-longpath" => SqliteVfs::Win32Longpath, - #[cfg(target_family = "windows")] - "win32-none" => SqliteVfs::Win32None, - #[cfg(target_family = "windows")] - "win32-longpath-none" => SqliteVfs::Win32LongpathNone, - _ => { - return Err(Error::Configuration( - format!("unknown value {:?} for `vfs`", s).into(), - )); - } - }) - } -} From e924c9fbc9b1f1ad5274efeca4f062bdec7c36c4 Mon Sep 17 00:00:00 2001 From: Lining Pan Date: Tue, 2 Aug 2022 18:31:30 -0400 Subject: [PATCH 3/3] fix: avoid unnecessary copies of vfs name --- sqlx-core/src/sqlite/options/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/sqlite/options/mod.rs b/sqlx-core/src/sqlite/options/mod.rs index 6c1813ae86..d5ec65c271 100644 --- a/sqlx-core/src/sqlite/options/mod.rs +++ b/sqlx-core/src/sqlite/options/mod.rs @@ -374,8 +374,8 @@ impl SqliteConnectOptions { /// /// The default value is empty, and sqlite will use the default VFS object dependeing on the /// operating system. - pub fn vfs(mut self, vfs_name: impl AsRef) -> Self { - self.vfs = Some(Cow::Owned(vfs_name.as_ref().to_string())); + pub fn vfs(mut self, vfs_name: impl Into>) -> Self { + self.vfs = Some(vfs_name.into()); self } }