use std::time::{SystemTime, UNIX_EPOCH};
use crate::identifier::Identifier;
use crate::APPLICATION_ID;
mod error;
pub use error::{SetVersionError, SetupError};
use rusqlite::{
functions::{Context, FunctionFlags},
params, Connection,
};
mod savepointable;
pub use savepointable::Savepointable;
mod as_connection;
pub use as_connection::AsConnection;
fn unixepoch(_: &Context) -> rusqlite::Result<i64> {
let now = SystemTime::now();
let stamp = now.duration_since(UNIX_EPOCH).map_or_else(
move |_| -(UNIX_EPOCH.duration_since(now).unwrap().as_secs() as i64),
move |dur| dur.as_secs() as i64,
);
Ok(stamp)
}
pub fn strict_without_rowid() -> &'static str {
match rusqlite::version_number() {
i32::MIN..=3008001 => "",
3008002..=3036999 => " WITHOUT ROWID",
3037000..=i32::MAX => " STRICT, WITHOUT ROWID",
}
}
pub fn strict() -> &'static str {
match rusqlite::version_number() {
i32::MIN..=3036999 => "",
3037000..=i32::MAX => " STRICT",
}
}
pub fn any() -> &'static str {
if rusqlite::version_number() < 3037000 {
"BLOB"
} else {
"ANY"
}
}
pub fn has_upsert() -> bool {
rusqlite::version_number() >= 3024000
}
pub fn setup(
connection: &Connection,
database: &Identifier,
table: &Identifier,
r#type: &str,
) -> Result<i64, SetupError> {
let application_id: i32 =
connection.query_row(&format!("PRAGMA {database}.application_id"), [], |row| {
row.get(0)
})?;
match application_id {
0 => {
connection.execute(
&format!("PRAGMA {database}.application_id = {APPLICATION_ID}"),
[],
)?;
}
crate::APPLICATION_ID => (),
id => {
return Err(SetupError::ApplicationId(id));
}
}
let mut user_version: i32 =
connection.query_row(&format!("PRAGMA {database}.user_version"), [], |row| {
row.get(0)
})?;
if user_version < 0 {
return Err(SetupError::UserVersion(user_version));
}
let prev_user_version = user_version;
if user_version < 1 {
let trailer = strict_without_rowid();
connection.execute(
&format!(
"CREATE TABLE {database}.collections (
name TEXT PRIMARY KEY NOT NULL,
type TEXT NOT NULL,
version INTEGER NOT NULL
){trailer}"
),
[],
)?;
user_version = 1;
}
if user_version > 1 {
return Err(SetupError::UserVersion(user_version));
}
if user_version != prev_user_version {
connection.execute(
&format!("PRAGMA {database}.user_version = {user_version}"),
[],
)?;
}
if rusqlite::version_number() < 3038000 {
connection.create_scalar_function(
"unixepoch",
0,
FunctionFlags::SQLITE_INNOCUOUS,
unixepoch,
)?;
}
{
let mut statement = connection.prepare_cached(&format!(
"SELECT type, version FROM {database}.collections WHERE name=?"
))?;
let table_str = &**table;
let version: i64 = match statement.query_row(params![table_str], |row| {
let r#type: String = row.get(0)?;
Ok((r#type, row.get(1)?))
}) {
Ok((prev_type, version)) => {
if r#type != prev_type {
return Err(SetupError::TableType {
expected: r#type.into(),
actual: prev_type,
});
}
version
}
Err(rusqlite::Error::QueryReturnedNoRows) => {
let mut statement = connection.prepare_cached(&format!(
"INSERT INTO {database}.collections (name, type, version) VALUES (?, ?, ?)"
))?;
statement.execute(params![table_str, r#type, 0i64])?;
0
}
Err(e) => return Err(e.into()),
};
Ok(version)
}
}
pub fn set_version(
connection: &Connection,
database: &Identifier,
table: &Identifier,
version: i64,
) -> Result<(), SetVersionError> {
let table_str = &**table;
let mut statement = connection.prepare_cached(&format!(
"UPDATE {database}.collections SET version=? WHERE name=?"
))?;
statement.execute(params![version, table_str])?;
Ok(())
}