use crate::data::Id; use crate::data::entities::EntityData; use crate::data::repositories::{BatchedDbLayer, DbLayer}; use crate::notification::MaybeError; use crate::notification::NotificationData::DbSaveError; use log::{debug, error}; use rusqlite::{Connection, Error, Params, Row}; use std::marker::PhantomData; use std::time::Instant; pub(crate) mod card_states; pub mod custom_fields; pub(crate) mod decks; pub(crate) mod history; pub(crate) mod import; pub(crate) mod settings; pub(crate) mod sqlite; pub(crate) mod voice; pub(crate) mod web_api; pub mod word_groups; pub(crate) mod words; pub trait TableAccessQuerySet { fn load() -> &'static str; fn save() -> &'static str; fn add() -> &'static str; fn name() -> &'static str; fn id_field() -> &'static str { "id" } fn from_sql(row: &Row) -> Result; fn insert_params(entity: &T) -> impl Params; fn update_params(entity: &T) -> impl Params; } #[derive(Clone)] pub struct SqliteDbLayer where Q: TableAccessQuerySet, { _phantom: PhantomData<(T, Q)>, } impl> Default for SqliteDbLayer { fn default() -> Self { Self { _phantom: PhantomData, } } } impl> DbLayer for SqliteDbLayer { fn write_entity(&self, entity: &T, connection: &Connection) -> MaybeError { let time = Instant::now(); connection .execute(Q::save(), Q::update_params(entity)) .map_err(|e| { error!("{}", e); DbSaveError })?; debug!("Updated in {} for {:?}", Q::name(), time.elapsed()); Ok(()) } fn add_entity(&mut self, entity: &mut T, connection: &Connection) -> MaybeError { let time = Instant::now(); let index: u32 = connection .query_row( format!("{} RETURNING {}", Q::add(), Q::id_field()).as_str(), Q::insert_params(entity), |row| row.get(0), ) .map_err(|e| { error!("{}", e); DbSaveError })?; entity.set_id(index.into()); debug!("Added into {} for {:?}", Q::name(), time.elapsed()); Ok(()) } fn delete_entity(&mut self, id: Id, connection: &Connection) -> MaybeError { let time = Instant::now(); if !id.is_valid() { return Err(DbSaveError); } connection .execute( format!("DELETE FROM {} WHERE id = ?1", Q::name()).as_str(), (&id,), ) .map_err(|e| { error!("{}", e); DbSaveError })?; debug!("Deleted from {} for {:?}", Q::name(), time.elapsed()); Ok(()) } fn load_all(&self, storage: &mut Vec, connection: &Connection) -> MaybeError { let time = Instant::now(); let mut stmt = connection .prepare(Q::load()) .expect("Failed to prepare state query"); let iter = stmt .query_map([], |row| Q::from_sql(row)) .map_err(|e| { error!("{}", e); DbSaveError })?; iter.filter_map(|res| res.ok()) .for_each(|i| storage.push(i)); debug!( "Loaded {} from {} for {:?}", storage.len(), Q::name(), time.elapsed() ); Ok(()) } } impl> BatchedDbLayer for SqliteDbLayer { fn delete_many_entities(&mut self, ids: &[Id], connection: &mut Connection) -> MaybeError { let time = Instant::now(); for chunk in ids.chunks(1000) { let stringed = chunk .iter() .map(|i| i.to_string()) .collect::>() .join(","); connection .execute( format!("DELETE FROM {} WHERE id in ({})", Q::name(), stringed).as_str(), (), ) .map_err(|e| { error!("{}", e); DbSaveError })?; } debug!( "Deleted {} from {} for {:?}", ids.len(), Q::name(), time.elapsed() ); Ok(()) } fn add_many_entities(&mut self, entities: &mut [T], connection: &mut Connection) -> MaybeError { let time = Instant::now(); let tx = connection .transaction() .expect("Failed to create transaction"); let count = entities.len(); { let mut stmt = tx.prepare(Q::add()).expect("Failed to prepare state query"); for entity in entities.iter() { stmt.execute(Q::insert_params(entity)).map_err(|e| { error!("{}", e); DbSaveError })?; } } let last_index: u32 = tx.last_insert_rowid() as u32; tx.commit().expect("Failed to commit"); let start_index = last_index - (count as u32) + 1; for (index, id) in (start_index..=last_index).enumerate() { entities[index].set_id(id.into()); } debug!( "Added {} into {} for {:?}", entities.len(), Q::name(), time.elapsed() ); Ok(()) } }