use std::io;
pub fn prepare_sqlite_path(dsn: &str, create_dirs: bool) -> io::Result<String> {
if dsn == "sqlite::memory:" || dsn == "sqlite://memory:" {
return Ok(dsn.to_owned());
}
if let Ok(url) = url::Url::parse(dsn) {
for (key, value) in url.query_pairs() {
if key.to_lowercase() == "mode" && value.to_lowercase() == "memory" {
return Ok(dsn.to_owned());
}
}
}
if !create_dirs {
return Ok(dsn.to_owned());
}
let file_path = extract_file_path_from_dsn(dsn);
if let Some(path) = file_path
&& let Some(parent) = path.parent()
{
std::fs::create_dir_all(parent)?;
}
Ok(dsn.to_owned())
}
fn extract_file_path_from_dsn(dsn: &str) -> Option<std::path::PathBuf> {
if dsn.contains("::memory:") || dsn.contains("//memory:") || dsn.contains("mode=memory") {
return None;
}
if let Ok(url) = url::Url::parse(dsn)
&& url.scheme() == "sqlite"
{
let path_str = url.path();
if path_str.is_empty() || path_str == "/" {
return None;
}
#[cfg(windows)]
{
let normalized_path = if path_str.len() > 3
&& path_str.starts_with('/')
&& path_str.chars().nth(2) == Some(':')
{
&path_str[1..] } else {
path_str
};
return Some(std::path::PathBuf::from(normalized_path));
}
#[cfg(not(windows))]
return Some(std::path::PathBuf::from(path_str));
}
if let Some(path_part) = dsn.strip_prefix("sqlite:") {
let path_part = path_part.trim_start_matches('/');
let path_part = if let Some(pos) = path_part.find('?') {
&path_part[..pos]
} else {
path_part
};
if !path_part.is_empty() && path_part != "memory:" {
return Some(std::path::PathBuf::from(path_part));
}
}
Some(std::path::PathBuf::from(dsn))
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::TempDir;
#[test]
fn test_extract_file_path_from_dsn() {
assert_eq!(
extract_file_path_from_dsn("sqlite:///absolute/path/to/db.sqlite"),
Some(PathBuf::from("/absolute/path/to/db.sqlite"))
);
assert_eq!(
extract_file_path_from_dsn("sqlite://./relative/path/to/db.sqlite"),
Some(PathBuf::from("/relative/path/to/db.sqlite"))
);
assert_eq!(
extract_file_path_from_dsn("sqlite:test.db"),
Some(PathBuf::from("test.db"))
);
assert_eq!(
extract_file_path_from_dsn("sqlite:///path/to/db.sqlite?wal=true"),
Some(PathBuf::from("/path/to/db.sqlite"))
);
assert_eq!(extract_file_path_from_dsn("sqlite::memory:"), None);
assert_eq!(extract_file_path_from_dsn("sqlite://memory:"), None);
assert_eq!(
extract_file_path_from_dsn("sqlite:///test.db?mode=memory"),
None
);
assert_eq!(
extract_file_path_from_dsn("/plain/file/path.db"),
Some(PathBuf::from("/plain/file/path.db"))
);
}
#[test]
fn test_prepare_sqlite_path_memory() {
assert_eq!(
prepare_sqlite_path("sqlite::memory:", true).unwrap(),
"sqlite::memory:"
);
assert_eq!(
prepare_sqlite_path("sqlite://memory:", false).unwrap(),
"sqlite://memory:"
);
assert_eq!(
prepare_sqlite_path("sqlite:///test.db?mode=memory", true).unwrap(),
"sqlite:///test.db?mode=memory"
);
}
#[test]
fn test_prepare_sqlite_path_no_create_dirs() {
let dsn = "sqlite:///some/path/db.sqlite";
assert_eq!(prepare_sqlite_path(dsn, false).unwrap(), dsn);
}
#[test]
fn test_prepare_sqlite_path_create_dirs() {
let temp_dir = TempDir::new().unwrap();
let test_path = temp_dir.path().join("db.sqlite");
let path_str = test_path.to_string_lossy().replace('\\', "/");
let dsn = format!("sqlite:///{}", path_str.trim_start_matches('/'));
let result = prepare_sqlite_path(&dsn, true);
assert!(result.is_ok(), "Failed to prepare path: {:?}", result.err());
assert_eq!(result.unwrap(), dsn);
let parent = test_path.parent().unwrap();
assert!(parent.exists(), "Parent directory should exist");
}
}