use crate::database::database::{DataBase, quoteIdentifier, validateIdentifier};
use crate::database::error::DbError;
use crate::database::query::QueryBuilder;
use crate::database::record::DbRecord;
use crate::database::value::SqlValue;
#[derive(Clone)]
pub struct Repository<T: DbRecord> {
db: DataBase,
_marker: std::marker::PhantomData<T>,
}
impl<T: DbRecord> Repository<T> {
pub(crate) fn new(db: DataBase) -> Result<Self, DbError> {
db.ensureTable::<T>()?;
for col in T::columns() {
validateIdentifier(col.name)?;
}
Ok(Self::attached(db))
}
pub(crate) fn attached(db: DataBase) -> Self {
Self {
db,
_marker: std::marker::PhantomData,
}
}
pub fn insert(&self, record: T) -> Result<i64, DbError> {
let mut ids: Vec<i64> = self.insert_many(vec![record])?;
ids.pop().ok_or_else(|| DbError::NotFound)
}
pub fn update(&self, id: i64, mutate: impl FnOnce(&mut T)) -> Result<bool, DbError> {
let mut record: T = match self.find(id)? {
Some(r) => r,
None => return Ok(false),
};
mutate(&mut record);
let params: Vec<(&str, SqlValue)> = record.toParams();
let set_clauses: Vec<String> = params
.iter()
.map(|(col, _)| format!("{} = ?", quoteIdentifier(col)))
.collect();
let table: String = quoteIdentifier(T::table_name());
let sql: String = format!(
"UPDATE {table} SET {} WHERE {} = ?",
set_clauses.join(", "),
quoteIdentifier("id"),
);
let mut values: Vec<SqlValue> = params.into_iter().map(|(_, v)| v).collect();
values.push(SqlValue::Integer(id));
let rows: usize = self
.db
.lock()
.execute(&sql, rusqlite::params_from_iter(values.iter()))?;
Ok(rows > 0)
}
pub fn delete(&self, id: i64) -> Result<bool, DbError> {
let table: String = quoteIdentifier(T::table_name());
let rows: usize = self.db.lock().execute(
&format!(
"UPDATE {table} SET {} = 1 WHERE {} = ?",
quoteIdentifier("deleted"),
quoteIdentifier("id"),
),
[id],
)?;
Ok(rows > 0)
}
pub fn delete_hard(&self, id: i64) -> Result<bool, DbError> {
let table: String = quoteIdentifier(T::table_name());
let rows: usize = self.db.lock().execute(
&format!("DELETE FROM {table} WHERE {} = ?", quoteIdentifier("id")),
[id],
)?;
Ok(rows > 0)
}
pub fn find(&self, id: i64) -> Result<Option<T>, DbError> {
self.query().where_eq("id", id).fetch_one()
}
pub fn find_many(&self, ids: &[i64]) -> Result<Vec<T>, DbError> {
if ids.is_empty() {
return Ok(vec![]);
}
self.query()
.where_in(
"id",
ids.iter().map(|id: &i64| SqlValue::Integer(*id)).collect(),
)
.fetch()
}
pub fn query(&self) -> QueryBuilder<T> {
QueryBuilder::new(self.db.clone())
}
pub fn insert_many(&self, records: Vec<T>) -> Result<Vec<i64>, DbError> {
let conn = self.db.lock();
conn.execute_batch("BEGIN;")?;
let mut ids = Vec::with_capacity(records.len());
for mut record in records {
let params = record.toParams();
let (col_sql, placeholders, values) = generate_insert_query(¶ms);
let table = quoteIdentifier(T::table_name());
let sql = format!("INSERT INTO {table} ({col_sql}) VALUES ({placeholders})");
conn.execute(&sql, rusqlite::params_from_iter(values.iter()))?;
let id = conn.last_insert_rowid();
record.set_id(id);
ids.push(id);
}
conn.execute_batch("COMMIT;")?;
Ok(ids)
}
}
fn generate_insert_query(params: &[(&'static str, SqlValue)]) -> (String, String, Vec<SqlValue>) {
let cols: String = params
.iter()
.map(|(c, _)| quoteIdentifier(c))
.collect::<Vec<_>>()
.join(", ");
let placeholders: String = params.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
let values: Vec<SqlValue> = params.iter().map(|(_, v)| v.clone()).collect();
(cols, placeholders, values)
}