2026-08-30 20:32:16 +03:00
|
|
|
use crate::data::Id;
|
2026-09-08 00:09:13 +03:00
|
|
|
use crate::data::entities::EntityData;
|
2026-08-30 20:32:16 +03:00
|
|
|
use crate::data::repositories::{BatchedDbLayer, DbLayer};
|
|
|
|
|
use crate::notification::MaybeError;
|
2026-09-08 00:09:13 +03:00
|
|
|
use crate::notification::NotificationData::DbSaveError;
|
2026-09-07 16:10:10 +03:00
|
|
|
use log::{debug, error};
|
2026-08-30 20:43:31 +03:00
|
|
|
use rusqlite::{Connection, Error, Params, Row};
|
2026-08-30 20:32:16 +03:00
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
use std::time::Instant;
|
|
|
|
|
|
2026-08-29 21:32:54 +03:00
|
|
|
pub(crate) mod card_states;
|
2026-08-31 11:26:05 +03:00
|
|
|
pub mod custom_fields;
|
2026-08-29 22:02:01 +03:00
|
|
|
pub(crate) mod decks;
|
2026-08-01 19:43:07 +03:00
|
|
|
pub(crate) mod history;
|
2026-08-15 21:32:24 +03:00
|
|
|
pub(crate) mod import;
|
2026-06-01 15:36:53 +03:00
|
|
|
pub(crate) mod settings;
|
2026-06-15 17:06:02 +03:00
|
|
|
pub(crate) mod sqlite;
|
2026-08-01 19:43:07 +03:00
|
|
|
pub(crate) mod voice;
|
2026-08-13 09:06:22 +03:00
|
|
|
pub(crate) mod web_api;
|
2026-08-31 11:25:41 +03:00
|
|
|
pub mod word_groups;
|
2026-08-31 11:26:05 +03:00
|
|
|
pub(crate) mod words;
|
2026-08-30 20:32:16 +03:00
|
|
|
|
|
|
|
|
pub trait TableAccessQuerySet<T> {
|
|
|
|
|
fn load() -> &'static str;
|
|
|
|
|
fn save() -> &'static str;
|
|
|
|
|
fn add() -> &'static str;
|
|
|
|
|
fn name() -> &'static str;
|
2026-09-08 00:09:13 +03:00
|
|
|
fn id_field() -> &'static str {
|
|
|
|
|
"id"
|
|
|
|
|
}
|
2026-08-30 20:43:31 +03:00
|
|
|
fn from_sql(row: &Row) -> Result<T, Error>;
|
2026-08-30 20:32:16 +03:00
|
|
|
fn insert_params(entity: &T) -> impl Params;
|
|
|
|
|
fn update_params(entity: &T) -> impl Params;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 12:12:14 +03:00
|
|
|
#[derive(Clone)]
|
2026-08-30 20:32:16 +03:00
|
|
|
pub struct SqliteDbLayer<T, Q>
|
|
|
|
|
where
|
|
|
|
|
Q: TableAccessQuerySet<T>,
|
|
|
|
|
{
|
2026-08-30 20:43:31 +03:00
|
|
|
_phantom: PhantomData<(T, Q)>,
|
2026-08-30 20:32:16 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-08 00:09:13 +03:00
|
|
|
impl<T, Q: TableAccessQuerySet<T>> Default for SqliteDbLayer<T, Q> {
|
2026-08-31 12:12:14 +03:00
|
|
|
fn default() -> Self {
|
2026-09-08 00:09:13 +03:00
|
|
|
Self {
|
2026-08-31 12:12:14 +03:00
|
|
|
_phantom: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q> {
|
|
|
|
|
fn write_entity(&self, entity: &T, connection: &Connection) -> MaybeError {
|
2026-08-30 22:36:43 +03:00
|
|
|
let time = Instant::now();
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
connection
|
2026-09-07 16:10:10 +03:00
|
|
|
.execute(Q::save(), Q::update_params(entity))
|
2026-09-08 00:09:13 +03:00
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-31 11:26:05 +03:00
|
|
|
debug!("Updated in {} for {:?}", Q::name(), time.elapsed());
|
2026-08-30 20:32:16 +03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_entity(&mut self, entity: &mut T, connection: &Connection) -> MaybeError {
|
2026-08-30 22:36:43 +03:00
|
|
|
let time = Instant::now();
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
let index: u32 = connection
|
2026-09-09 15:00:52 +03:00
|
|
|
.query_row(
|
|
|
|
|
format!("{} RETURNING {}", Q::add(), Q::id_field()).as_str(),
|
|
|
|
|
Q::insert_params(entity),
|
|
|
|
|
|row| row.get(0),
|
|
|
|
|
)
|
2026-09-08 00:09:13 +03:00
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-30 20:32:16 +03:00
|
|
|
|
|
|
|
|
entity.set_id(index.into());
|
2026-08-31 11:26:05 +03:00
|
|
|
debug!("Added into {} for {:?}", Q::name(), time.elapsed());
|
2026-08-30 20:32:16 +03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn delete_entity(&mut self, id: Id, connection: &Connection) -> MaybeError {
|
2026-08-30 22:36:43 +03:00
|
|
|
let time = Instant::now();
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
if !id.is_valid() {
|
|
|
|
|
return Err(DbSaveError);
|
|
|
|
|
}
|
|
|
|
|
connection
|
|
|
|
|
.execute(
|
|
|
|
|
format!("DELETE FROM {} WHERE id = ?1", Q::name()).as_str(),
|
|
|
|
|
(&id,),
|
|
|
|
|
)
|
2026-09-08 00:09:13 +03:00
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-31 11:26:05 +03:00
|
|
|
debug!("Deleted from {} for {:?}", Q::name(), time.elapsed());
|
2026-08-30 20:32:16 +03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_all(&self, storage: &mut Vec<T>, connection: &Connection) -> MaybeError {
|
2026-08-30 22:36:43 +03:00
|
|
|
let time = Instant::now();
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
let mut stmt = connection
|
|
|
|
|
.prepare(Q::load())
|
|
|
|
|
.expect("Failed to prepare state query");
|
2026-09-11 00:01:19 +03:00
|
|
|
let iter = stmt.query_map([], |row| Q::from_sql(row)).map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-30 20:32:16 +03:00
|
|
|
|
|
|
|
|
iter.filter_map(|res| res.ok())
|
|
|
|
|
.for_each(|i| storage.push(i));
|
2026-08-31 11:21:01 +03:00
|
|
|
debug!(
|
2026-08-30 22:36:43 +03:00
|
|
|
"Loaded {} from {} for {:?}",
|
|
|
|
|
storage.len(),
|
|
|
|
|
Q::name(),
|
|
|
|
|
time.elapsed()
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: EntityData, Q: TableAccessQuerySet<T>> BatchedDbLayer<T> for SqliteDbLayer<T, Q> {
|
|
|
|
|
fn delete_many_entities(&mut self, ids: &[Id], connection: &mut Connection) -> MaybeError {
|
2026-08-30 22:36:43 +03:00
|
|
|
let time = Instant::now();
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
for chunk in ids.chunks(1000) {
|
|
|
|
|
let stringed = chunk
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|i| i.to_string())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(",");
|
|
|
|
|
connection
|
|
|
|
|
.execute(
|
|
|
|
|
format!("DELETE FROM {} WHERE id in ({})", Q::name(), stringed).as_str(),
|
|
|
|
|
(),
|
|
|
|
|
)
|
2026-09-08 00:09:13 +03:00
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-30 20:32:16 +03:00
|
|
|
}
|
2026-08-31 11:21:01 +03:00
|
|
|
debug!(
|
2026-08-30 22:36:43 +03:00
|
|
|
"Deleted {} from {} for {:?}",
|
|
|
|
|
ids.len(),
|
|
|
|
|
Q::name(),
|
|
|
|
|
time.elapsed()
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-30 20:32:16 +03:00
|
|
|
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() {
|
2026-09-08 00:09:13 +03:00
|
|
|
stmt.execute(Q::insert_params(entity)).map_err(|e| {
|
|
|
|
|
error!("{}", e);
|
|
|
|
|
DbSaveError
|
|
|
|
|
})?;
|
2026-08-30 20:32:16 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 11:21:01 +03:00
|
|
|
debug!(
|
2026-08-30 22:36:43 +03:00
|
|
|
"Added {} into {} for {:?}",
|
|
|
|
|
entities.len(),
|
|
|
|
|
Q::name(),
|
|
|
|
|
time.elapsed()
|
|
|
|
|
);
|
2026-08-30 20:32:16 +03:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|