use crate::error::Error;
use crate::sqlite::SqliteConnectOptions;
use percent_encoding::percent_decode_str;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering};
static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0);
impl FromStr for SqliteConnectOptions {
type Err = Error;
fn from_str(mut uri: &str) -> Result<Self, Self::Err> {
let mut options = Self::new();
uri = uri
.trim_start_matches("sqlite://")
.trim_start_matches("sqlite:");
let mut database_and_params = uri.splitn(2, '?');
let database = database_and_params.next().unwrap_or_default();
if database == ":memory:" {
options.in_memory = true;
options.shared_cache = true;
let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed);
options.filename = Cow::Owned(PathBuf::from(format!("file:sqlx-in-memory-{}", seqno)));
} else {
options.filename = Cow::Owned(
Path::new(
&*percent_decode_str(database)
.decode_utf8()
.map_err(Error::config)?,
)
.to_path_buf(),
);
}
if let Some(params) = database_and_params.next() {
for (key, value) in url::form_urlencoded::parse(params.as_bytes()) {
match &*key {
"mode" => {
match &*value {
"ro" => {
options.read_only = true;
}
"rw" => {}
"rwc" => {
options.create_if_missing = true;
}
"memory" => {
options.in_memory = true;
options.shared_cache = true;
}
_ => {
return Err(Error::Configuration(
format!("unknown value {:?} for `mode`", value).into(),
));
}
}
}
"cache" => match &*value {
"private" => {
options.shared_cache = false;
}
"shared" => {
options.shared_cache = true;
}
_ => {
return Err(Error::Configuration(
format!("unknown value {:?} for `cache`", value).into(),
));
}
},
_ => {
return Err(Error::Configuration(
format!(
"unknown query parameter `{}` while parsing connection URI",
key
)
.into(),
));
}
}
}
}
Ok(options)
}
}
#[test]
fn test_parse_in_memory() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite::memory:".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://:memory:".parse()?;
assert!(options.in_memory);
assert!(options.shared_cache);
let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?;
assert!(options.in_memory);
assert!(!options.shared_cache);
Ok(())
}
#[test]
fn test_parse_read_only() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?;
assert!(options.read_only);
assert_eq!(&*options.filename.to_string_lossy(), "a.db");
Ok(())
}
#[test]
fn test_parse_shared_in_memory() -> Result<(), Error> {
let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?;
assert!(options.shared_cache);
assert_eq!(&*options.filename.to_string_lossy(), "a.db");
Ok(())
}