use std::marker::PhantomData;
use rusqlite::{params, Row};
use thiserror::Error;
#[cfg(feature="tracing")]
use tracing::debug;
use crate::{Database, Res};
pub trait Table {
const TBL_NAME: &str;
const CREATE_QUERY: &str;
const TABLE_SCHEMA_QUERY: &str = "SELECT sql FROM sqlite_schema WHERE tbl_name=?1 AND type='table' AND name NOT LIKE 'sqlite_%'";
fn tbl_name() -> &'static str {
Self::TBL_NAME
}
fn create_table(database: &Database) -> Res<bool> {
if Self::CREATE_QUERY
.starts_with(format!("CREATE TABLE IF NOT EXISTS {}", Self::TBL_NAME).as_str())
{
debug!("Skip on not exists");
} else if Self::CREATE_QUERY
.starts_with(format!("CREATE TABLE {}", Self::TBL_NAME).as_str())
{
debug!("Fail on Exists");
} else {
return Err(format!(
"'{}' is not a valid CREATE TABLE command",
Self::CREATE_QUERY
)
.into());
};
database.execute(Self::CREATE_QUERY)?;
Ok(true)
}
fn exists_db(database: &Database) -> Res<bool> {
let func = |row: &Row| -> Result<String, rusqlite::Error> { row.get(0) };
let mut stmt = database.prepare_reader(Self::TABLE_SCHEMA_QUERY)?;
let rows: Box<[String]> = stmt
.query_map(params![Self::TBL_NAME], func)?
.flatten()
.collect();
if rows.len() == 1 {
Ok(true)
} else if rows.is_empty() {
Ok(false)
} else {
let path = database.path_str();
Err(format!(
"There are {} tables with {} table_name at the db at {}",
rows.len(),
Self::TBL_NAME,
path
)
.into())
}
}
fn is_initialized(database: &Database) -> Res<()> {
let mut stmt = database.prepare_reader(Self::TABLE_SCHEMA_QUERY)?;
let current_schema = stmt.query_row(
params![Self::TBL_NAME],
|row| -> Result<String, rusqlite::Error> { row.get(0) },
)?;
if current_schema == Self::CREATE_QUERY {
Ok(())
} else {
Err(format!(
"Expected {} schema, got {} schema for {} table",
Self::CREATE_QUERY,
current_schema,
Self::TBL_NAME
)
.into())
}
}
fn len(database: &Database) -> Res<usize> {
let func = |row: &Row| -> Result<usize, rusqlite::Error> { row.get(1) };
let mut stmt = database.prepare_reader("SELECT COUNT(*) FROM ?1")?;
let len = stmt.query_row(params![Self::TBL_NAME], func)?;
Ok(len)
}
fn is_empty(database: &Database) -> Res<bool> {
Ok(Self::len(database)? > 1)
}
fn select_asterisk<R, F: Fn(&Row) -> Result<R, rusqlite::Error>>(
database: &Database,
func: F,
) -> Res<Vec<R>> {
let mut stmt = database.prepare_reader("SELECT * FROM ?1")?;
let rows = stmt
.query_map(params![Self::TBL_NAME], func)?
.flatten()
.collect();
Ok(rows)
}
}
pub struct OwnedTable<T: Table>(PhantomData<T>);
impl<T: Table> OwnedTable<T> {
pub fn create(database: &Database) -> Res<Self> {
if !T::exists_db(database)? {
T::create_table(database)?;
};
T::is_initialized(database)?;
Ok(Self(PhantomData))
}
}
#[derive(Error, Debug)]
pub enum OwnedTableError {
#[error("Old Schema:\n{0}\nNew Schema:\n{1}")]
MismatchedSchema(String, String),
}