Write SqlDbLayer
This commit is contained in:
1 parent
8ec7c8f05d
commit
45830a7985
7 files changed
+149
-18
No files matched your search
+1
-1
@@ -3,7 +3,7 @@ pub mod repositories;
|
|||||||
|
|
||||||
use rusqlite::ToSql;
|
use rusqlite::ToSql;
|
||||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{write, Display, Formatter};
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ impl CardStatistics {
|
|||||||
#[derive(Copy)]
|
#[derive(Copy)]
|
||||||
pub struct EntityHandle<E> {
|
pub struct EntityHandle<E> {
|
||||||
pub(crate) id: Id,
|
pub(crate) id: Id,
|
||||||
index: i32,
|
index: u32,
|
||||||
pub(crate) _phantom: PhantomData<E>,
|
pub(crate) _phantom: PhantomData<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ impl<T> EntityHandle<T> {
|
|||||||
pub fn new(id: Id, index: usize, real: bool) -> Self {
|
pub fn new(id: Id, index: usize, real: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
index: index as i32 | (!real as i32) << 31,
|
index: ((index as u32) << 1 >> 1) | (!real as u32) << 31,
|
||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ impl<T> EntityHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_index(&mut self, index: usize) {
|
pub fn set_index(&mut self, index: usize) {
|
||||||
self.index = self.index >> 31 << 31 | index as i32;
|
self.index = (self.index >> 31 << 31) | ((index as u32) << 1 >> 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn real(&self) -> bool {
|
pub fn real(&self) -> bool {
|
||||||
@@ -219,7 +219,7 @@ impl<T> EntityHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_real(&mut self, value: bool) {
|
pub fn set_real(&mut self, value: bool) {
|
||||||
self.index = self.index << 1 >> 1 | (!value as i32) << 31;
|
self.index = self.index << 1 >> 1 | (!value as u32) << 31;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> Eq for EntityHandle<T> {}
|
impl<T> Eq for EntityHandle<T> {}
|
||||||
|
|||||||
@@ -178,8 +178,8 @@ pub trait BatchedDbLayer<T>
|
|||||||
where
|
where
|
||||||
T: EntityData,
|
T: EntityData,
|
||||||
{
|
{
|
||||||
fn delete_many_entities(&mut self, ids: &[Id], connection: &Connection) -> MaybeError;
|
fn delete_many_entities(&mut self, ids: &[Id], connection: &mut Connection) -> MaybeError;
|
||||||
fn add_many_entities(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError;
|
fn add_many_entities(&mut self, entity: &mut [T], connection: &mut Connection) -> MaybeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -414,7 +414,7 @@ impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.data_layer
|
self.data_layer
|
||||||
.add_many_entities(&mut temp_values, &self.connection.read())?;
|
.add_many_entities(&mut temp_values, &mut self.connection.write())?;
|
||||||
|
|
||||||
let start = self.cache.len();
|
let start = self.cache.len();
|
||||||
|
|
||||||
@@ -456,7 +456,7 @@ impl<T: EntityData + Clone, D: BatchedDbLayer<T> + DbLayer<T>> BatchedRepository
|
|||||||
!remove
|
!remove
|
||||||
});
|
});
|
||||||
self.data_layer
|
self.data_layer
|
||||||
.delete_many_entities(cached.as_slice(), &self.connection.read())?;
|
.delete_many_entities(cached.as_slice(), &mut self.connection.write())?;
|
||||||
let len = cached.len();
|
let len = cached.len();
|
||||||
cell = 0;
|
cell = 0;
|
||||||
self.cache.retain(|x| {
|
self.cache.retain(|x| {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub fn load_stats_of_deck(
|
|||||||
score: row.get(2)?,
|
score: row.get(2)?,
|
||||||
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(4)?)
|
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(4)?)
|
||||||
.expect("Incorrect date encoding"),
|
.expect("Incorrect date encoding"),
|
||||||
fsrs_memory: Some(get_memory(row.get(5)?)),
|
fsrs_memory: get_memory(row.get(5)?),
|
||||||
last_open: row.get(3)?,
|
last_open: row.get(3)?,
|
||||||
fsrs_open_day: NaiveDate::from_epoch_days(row.get::<usize, i32>(6)?)
|
fsrs_open_day: NaiveDate::from_epoch_days(row.get::<usize, i32>(6)?)
|
||||||
.expect("Incorrect date encoding"),
|
.expect("Incorrect date encoding"),
|
||||||
@@ -37,14 +37,21 @@ pub fn load_stats_of_deck(
|
|||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_memory(value: i64) -> MemoryState {
|
fn get_memory(value: i64) -> Option<MemoryState> {
|
||||||
MemoryState {
|
if value == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(MemoryState {
|
||||||
stability: f32::from_bits(value as u32),
|
stability: f32::from_bits(value as u32),
|
||||||
difficulty: f32::from_bits((value >> 32) as u32),
|
difficulty: f32::from_bits((value >> 32) as u32),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_to_i64(value: &MemoryState) -> i64 {
|
fn memory_to_i64(value: &Option<MemoryState>) -> i64 {
|
||||||
|
let Some(value) = &value else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32)
|
value.stability.to_bits() as i64 | ((value.difficulty.to_bits() as i64) << 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +76,7 @@ pub fn add_stat_list(stats: &mut [CardStatistics], connection: &mut Connection)
|
|||||||
stat.score,
|
stat.score,
|
||||||
stat.last_open.timestamp(),
|
stat.last_open.timestamp(),
|
||||||
stat.fsrs_opened.to_epoch_days(),
|
stat.fsrs_opened.to_epoch_days(),
|
||||||
memory_to_i64(&stat.fsrs_memory.unwrap_or(MemoryState {
|
memory_to_i64(&stat.fsrs_memory),
|
||||||
difficulty: 0.0,
|
|
||||||
stability: 0.0
|
|
||||||
})),
|
|
||||||
stat.fsrs_open_day.to_epoch_days()
|
stat.fsrs_open_day.to_epoch_days()
|
||||||
])
|
])
|
||||||
.map_err(|_| DbSaveError)?;
|
.map_err(|_| DbSaveError)?;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
|||||||
use crate::notification::{MaybeError, NotificationData};
|
use crate::notification::{MaybeError, NotificationData};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
|
|
||||||
pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> {
|
pub fn load_sets(connection: &Connection) -> Result<Vec<DeckSettings>, NotificationData> {
|
||||||
let mut stmt = connection
|
let mut stmt = connection
|
||||||
.prepare("SELECT id, name, forward, backward, filter FROM decks")
|
.prepare("SELECT id, name, forward, backward, filter FROM decks")
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
use crate::core::srs::OrderMode;
|
||||||
|
use crate::data::Id;
|
||||||
|
use crate::data::entities::{DeckSettings, EntityData};
|
||||||
|
use crate::data::repositories::{BatchedDbLayer, DbLayer};
|
||||||
|
use crate::notification::MaybeError;
|
||||||
|
use crate::notification::NotificationData::{DbLoadError, DbSaveError};
|
||||||
|
use log::info;
|
||||||
|
use rusqlite::{Connection, Params, Row, params};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
pub(crate) mod card_states;
|
pub(crate) mod card_states;
|
||||||
pub(crate) mod decks;
|
pub(crate) mod decks;
|
||||||
pub(crate) mod history;
|
pub(crate) mod history;
|
||||||
@@ -7,3 +18,112 @@ pub(crate) mod sqlite;
|
|||||||
pub(crate) mod voice;
|
pub(crate) mod voice;
|
||||||
pub(crate) mod web_api;
|
pub(crate) mod web_api;
|
||||||
pub(crate) mod words;
|
pub(crate) mod words;
|
||||||
|
|
||||||
|
pub trait TableAccessQuerySet<T> {
|
||||||
|
fn load() -> &'static str;
|
||||||
|
fn save() -> &'static str;
|
||||||
|
fn add() -> &'static str;
|
||||||
|
fn name() -> &'static str;
|
||||||
|
fn from_sql(row: &Row) -> T;
|
||||||
|
fn insert_params(entity: &T) -> impl Params;
|
||||||
|
fn update_params(entity: &T) -> impl Params;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SqliteDbLayer<T, Q>
|
||||||
|
where
|
||||||
|
Q: TableAccessQuerySet<T>,
|
||||||
|
{
|
||||||
|
query_set: Q,
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EntityData, Q: TableAccessQuerySet<T>> DbLayer<T> for SqliteDbLayer<T, Q> {
|
||||||
|
fn write_entity(&self, entity: &T, connection: &Connection) -> MaybeError {
|
||||||
|
connection
|
||||||
|
.execute(Q::load(), Q::update_params(entity))
|
||||||
|
.map_err(|_| DbSaveError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_entity(&mut self, entity: &mut T, connection: &Connection) -> MaybeError {
|
||||||
|
let index: u32 = connection
|
||||||
|
.query_row(Q::add(), Q::insert_params(entity), |row| row.get(0))
|
||||||
|
.map_err(|_| DbSaveError)?;
|
||||||
|
|
||||||
|
entity.set_id(index.into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_entity(&mut self, id: Id, connection: &Connection) -> MaybeError {
|
||||||
|
if !id.is_valid() {
|
||||||
|
return Err(DbSaveError);
|
||||||
|
}
|
||||||
|
connection
|
||||||
|
.execute(
|
||||||
|
format!("DELETE FROM {} WHERE id = ?1", Q::name()).as_str(),
|
||||||
|
(&id,),
|
||||||
|
)
|
||||||
|
.map_err(|_| DbSaveError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_all(&self, storage: &mut Vec<T>, connection: &Connection) -> MaybeError {
|
||||||
|
let mut stmt = connection
|
||||||
|
.prepare(Q::load())
|
||||||
|
.expect("Failed to prepare state query");
|
||||||
|
let iter = stmt
|
||||||
|
.query_map([], |row| Ok(Q::from_sql(row)))
|
||||||
|
.map_err(|_| DbLoadError)?;
|
||||||
|
|
||||||
|
iter.filter_map(|res| res.ok())
|
||||||
|
.for_each(|i| storage.push(i));
|
||||||
|
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 {
|
||||||
|
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(),
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.map_err(|_| DbSaveError)?;
|
||||||
|
}
|
||||||
|
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(|_| 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Added {} entities for {:?}", entities.len(), time.elapsed());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,4 +65,10 @@ pub fn default_connection() -> Connection {
|
|||||||
.execute("PRAGMA foreign_keys = ON;", [])
|
.execute("PRAGMA foreign_keys = ON;", [])
|
||||||
.expect("Failed to set PRAGMA");
|
.expect("Failed to set PRAGMA");
|
||||||
connection
|
connection
|
||||||
|
.execute("PRAGMA journal_mode = WAL;", [])
|
||||||
|
.expect("Failed to set PRAGMA");
|
||||||
|
connection
|
||||||
|
.execute("PRAGMA synchronous = NORMAL;", [])
|
||||||
|
.expect("Failed to set PRAGMA");
|
||||||
|
connection
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user