pub mod migration;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use sea_orm_migration::MigratorTrait;
use bitrouter_sdk::{BitrouterError, Result};
pub async fn connect(url: &str) -> Result<DatabaseConnection> {
let mut opts = ConnectOptions::new(normalize_url(url));
opts.sqlx_logging(false);
if is_sqlite_memory(url) {
opts.min_connections(1).max_connections(1);
}
Database::connect(opts)
.await
.map_err(|e| BitrouterError::internal(format!("connecting to database {url}: {e}")))
}
pub async fn run_migrations(db: &DatabaseConnection) -> Result<()> {
migration::Migrator::up(db, None)
.await
.map_err(|e| BitrouterError::internal(format!("running database migrations: {e}")))?;
Ok(())
}
fn is_sqlite_memory(url: &str) -> bool {
url.starts_with("sqlite:") && url.contains(":memory:")
}
fn normalize_url(url: &str) -> String {
let is_sqlite_file =
url.starts_with("sqlite:") && !url.contains(":memory:") && !url.contains("mode=");
if is_sqlite_file {
let sep = if url.contains('?') { '&' } else { '?' };
format!("{url}{sep}mode=rwc")
} else {
url.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sqlite_file_urls_get_mode_rwc() {
assert_eq!(
normalize_url("sqlite://./bitrouter.db"),
"sqlite://./bitrouter.db?mode=rwc"
);
assert_eq!(
normalize_url("sqlite://./bitrouter.db?cache=shared"),
"sqlite://./bitrouter.db?cache=shared&mode=rwc"
);
}
#[test]
fn explicit_mode_and_non_sqlite_urls_are_left_alone() {
assert_eq!(
normalize_url("sqlite://./x.db?mode=ro"),
"sqlite://./x.db?mode=ro"
);
assert_eq!(normalize_url("sqlite::memory:"), "sqlite::memory:");
assert_eq!(
normalize_url("postgres://u:p@host/db"),
"postgres://u:p@host/db"
);
assert_eq!(normalize_url("mysql://u:p@host/db"), "mysql://u:p@host/db");
}
#[test]
fn detects_in_memory_sqlite() {
assert!(is_sqlite_memory("sqlite::memory:"));
assert!(is_sqlite_memory("sqlite://:memory:"));
assert!(!is_sqlite_memory("sqlite://./bitrouter.db"));
assert!(!is_sqlite_memory("postgres://host/db"));
}
}