#[cfg(all(not(feature = "test"), any(test, doctest)))]
compile_error!("turbosql must be tested with '--features test'");
#[cfg(all(feature = "test", doctest))]
doc_comment::doctest!("../../README.md");
use itertools::{
EitherOrBoth::{Both, Left, Right},
Itertools,
};
use rusqlite::{Connection, OpenFlags};
use serde::Deserialize;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
#[doc(hidden)]
pub use once_cell::sync::Lazy;
#[doc(hidden)]
pub use rusqlite::{
params, types::FromSql, types::FromSqlResult, types::ToSql, types::ToSqlOutput, types::Value,
types::ValueRef, Error, OptionalExtension, Result,
};
#[doc(hidden)]
pub use serde::Serialize;
pub use turbosql_impl::{execute, select, Turbosql};
pub type Blob = Vec<u8>;
#[derive(Clone, Debug, Deserialize, Default)]
struct MigrationsToml {
migrations_append_only: Option<Vec<String>>,
autogenerated_schema_for_your_information_do_not_edit: Option<String>,
}
struct DbPath {
path: PathBuf,
opened: bool,
}
static __DB_PATH: Lazy<Mutex<DbPath>> = Lazy::new(|| {
#[cfg(not(feature = "test"))]
let path = {
let exe_stem = std::env::current_exe().unwrap().file_stem().unwrap().to_owned();
let exe_stem_lossy = exe_stem.to_string_lossy();
let path = directories_next::ProjectDirs::from("org", &exe_stem_lossy, &exe_stem_lossy)
.unwrap()
.data_dir()
.to_owned();
std::fs::create_dir_all(&path).unwrap();
path.join(exe_stem).with_extension("sqlite")
};
#[cfg(feature = "test")]
let path = Path::new(":memory:").to_owned();
Mutex::new(DbPath { path, opened: false })
});
#[doc(hidden)]
pub static __TURBOSQL_DB: Lazy<Mutex<Connection>> = Lazy::new(|| {
#[cfg(not(feature = "test"))]
let toml_decoded: MigrationsToml =
toml::from_str(include_str!(concat!(env!("OUT_DIR"), "/../../../../../migrations.toml")))
.expect("Unable to decode embedded migrations.toml");
#[cfg(feature = "test")]
let toml_decoded: MigrationsToml =
toml::from_str(include_str!("../../test.migrations.toml")).unwrap();
let target_migrations = toml_decoded.migrations_append_only.unwrap_or_else(Vec::new);
let mut db_path = __DB_PATH.lock().unwrap();
db_path.opened = true;
let conn = Connection::open_with_flags(
&db_path.path,
OpenFlags::SQLITE_OPEN_READ_WRITE
| OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_NO_MUTEX,
)
.expect("rusqlite::Connection::open_with_flags");
conn
.execute_batch(
r#"
PRAGMA auto_vacuum=INCREMENTAL;
PRAGMA journal_mode=WAL;
PRAGMA wal_autocheckpoint=8000;
PRAGMA synchronous=NORMAL;
"#,
)
.expect("Execute PRAGMAs");
let result = conn.query_row(
"SELECT sql FROM sqlite_master WHERE name = ?",
params!["turbosql_migrations"],
|row| {
let sql: String = row.get(0).unwrap();
Ok(sql)
},
);
match result {
Err(rusqlite::Error::QueryReturnedNoRows) => {
conn
.execute_batch(
r#"CREATE TABLE turbosql_migrations (rowid INTEGER PRIMARY KEY, migration TEXT NOT NULL)"#,
)
.expect("CREATE TABLE turbosql_migrations");
}
Err(err) => {
panic!(err);
}
Ok(_) => (),
}
let applied_migrations = conn
.prepare("SELECT migration FROM turbosql_migrations ORDER BY rowid")
.unwrap()
.query_map(params![], |row| {
Ok(row.get(0).unwrap())
})
.unwrap()
.map(|x| x.unwrap())
.collect::<Vec<String>>();
applied_migrations.iter().zip_longest(&target_migrations).for_each(|item| match item {
Both(a, b) => {
if a != b {
panic!("Mismatch in Turbosql migrations!")
}
}
Left(_) => panic!("More migrations are applied than target"),
Right(migration) => {
if !migration.starts_with("--") {
conn.execute(migration, params![]).unwrap();
}
conn
.execute("INSERT INTO turbosql_migrations(migration) VALUES(?)", params![migration])
.unwrap();
}
});
Mutex::new(conn)
});
pub fn set_db_path(path: &Path) -> Result<(), anyhow::Error> {
let mut db_path = __DB_PATH.lock().unwrap();
if db_path.opened {
return Err(anyhow::anyhow!("Trying to set path when DB is already opened"));
}
db_path.path = path.to_owned();
Ok(())
}