use rusqlite::{Connection, Row, ToSql};
use std::marker::PhantomData;
use crate::entities::errors::{DbError, DbResult};
use crate::entities::types::SStr;
use crate::impls::executor::Executor;
use crate::traits::repo::{IConnection, IDbRepo, IExecutor, INewDbRepo};
use crate::traits::table::ITable;
pub struct TableManager<Cnn, Tbl> {
pub connection: Cnn,
table: PhantomData<Tbl>,
}
impl<Cnn: Clone, Tbl> Clone for TableManager<Cnn, Tbl> {
fn clone(&self) -> Self {
Self {
connection: self.connection.clone(),
table: Default::default(),
}
}
}
impl<Cnn: IConnection, Tbl: ITable> TableManager<Cnn, Tbl> {
fn create_query(&self) -> String {
let columns = Tbl::COLUMNS
.iter()
.map(|(c_name, c_type)| format!("`{}` {}", c_name, c_type));
let unique = Tbl::UNIQUE.iter().map(Self::wrap_unique);
let f_keys = Tbl::FOREIGN_KEYS.iter().map(|(column, table, ext_column)| {
format!(
"FOREIGN KEY (`{}`) REFERENCES `{}`(`{}`)",
column, table, ext_column,
)
});
let attrs = columns
.chain(unique)
.chain(f_keys)
.collect::<Vec<_>>()
.join(", ");
format!("CREATE TABLE IF NOT EXISTS `{}` ({})", Tbl::NAME, attrs,)
}
fn wrap_unique(fields: &SStr) -> String {
let fields = fields
.split(',')
.map(|field| format!("`{}`", field.trim()))
.collect::<Vec<_>>()
.join(",");
format!("UNIQUE({})", fields)
}
fn make_index_name(&self, field: &str) -> String {
format!("{}_{}_index", Tbl::NAME, field)
}
}
impl<Cnn: IConnection, Tbl: ITable> INewDbRepo<Cnn> for TableManager<Cnn, Tbl> {
fn create(connection: Cnn) -> Self {
Self {
connection,
table: Default::default(),
}
}
}
impl<Cnn: IConnection, Tbl: ITable> IDbRepo for TableManager<Cnn, Tbl> {
fn init(&self) -> DbResult<()> {
match self.execute(&self.create_query(), &[]) {
Err(DbError::Other(msg)) => Err(DbError::CanNotInitTable(Tbl::NAME, msg)),
other => other,
}
}
fn drop(&self) -> DbResult<()> {
let query = format!("DROP TABLE IF EXISTS `{}`", Tbl::NAME);
self.execute(&query, &[])
}
fn set_indexes(&self) -> DbResult<()> {
let fun = |cnn: &Connection| -> DbResult<()> {
for column in Tbl::INDEXES.iter() {
let index = self.make_index_name(column);
let query = format!(
"CREATE INDEX IF NOT EXISTS {} ON `{}` (`{}`)",
index,
Tbl::NAME,
column,
);
cnn.execute(&query, ())?;
}
Ok(())
};
self.connection.with(fun)
}
fn drop_indexes(&self) -> DbResult<()> {
let fun = |cnn: &Connection| -> DbResult<()> {
for column in Tbl::INDEXES.iter() {
let index = self.make_index_name(column);
let query = format!("DROP INDEX IF EXISTS {}", index);
cnn.execute(&query, ())?;
}
Ok(())
};
self.connection.with(fun)
}
fn get_size(&self) -> DbResult<usize> {
let query = format!("SELECT COUNT(*) FROM `{}`", Tbl::NAME);
let fun = move |cnn: &Connection| -> DbResult<usize> {
let mut stm = cnn.prepare(&query)?;
let mut rows = stm.raw_query();
Ok(rows.next()?.unwrap().get(0)?)
};
self.connection.with(fun)
}
}
impl<Cnn: IConnection, Tbl: ITable> IExecutor for TableManager<Cnn, Tbl> {
type Locked = TableManager<Cnn::Locked, Tbl>;
fn lock(&self) -> DbResult<Self::Locked> {
Ok(TableManager {
connection: self.connection.lock()?,
table: self.table.clone(),
})
}
fn get_one<T, F: FnMut(&Row<'_>) -> rusqlite::Result<T>>(
&self,
query: &str,
params: &[&dyn ToSql],
serializer: F,
) -> DbResult<T> {
Executor::new(&self.connection).get_one(query, params, serializer)
}
fn get_many<T, F: FnMut(&Row<'_>) -> rusqlite::Result<T>>(
&self,
query: &str,
params: &[&dyn ToSql],
serializer: F,
) -> DbResult<Vec<T>> {
Executor::new(&self.connection).get_many(query, params, serializer)
}
fn execute(&self, query: &str, params: &[&dyn ToSql]) -> DbResult<()> {
Executor::new(&self.connection).execute(query, params)
}
fn execute_return_id(&self, query: &str, params: &[&dyn ToSql]) -> DbResult<i64> {
Executor::new(&self.connection).execute_return_id(query, params)
}
}