Db layers #14

Merged
micialware merged 7 commits from db_layers into master 2026-08-30 23:38:33 +03:00
8 changed files with 154 additions and 27 deletions
Showing only changes of commit d9bbf9e705 - Show all commits

No files matched your search

Generated
+6 -2
View File
@@ -2456,6 +2456,7 @@ dependencies = [
"hex", "hex",
"iced", "iced",
"iced_core", "iced_core",
"litemap",
"log", "log",
"mimalloc", "mimalloc",
"parking_lot", "parking_lot",
@@ -2739,9 +2740,12 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
dependencies = [
"serde_core",
]
[[package]] [[package]]
name = "litrs" name = "litrs"
+1
View File
@@ -27,6 +27,7 @@ parking_lot = "0.12.5"
env_logger = "0.11.11" env_logger = "0.11.11"
log = "0.4.34" log = "0.4.34"
fsrs = "6.6.1" fsrs = "6.6.1"
litemap = { version = "0.8.3", features = ["serde"] }
[profile.super-release] [profile.super-release]
inherits = "release" inherits = "release"
+1
View File
@@ -19,6 +19,7 @@ use rusqlite::Connection;
pub const USER_FONT: Font = Font::with_name("Noto Sans JP"); pub const USER_FONT: Font = Font::with_name("Noto Sans JP");
pub fn start() -> iced::Result { pub fn start() -> iced::Result {
println!("{}", size_of::<WordData>());
info!("Welcome to JapLearn"); info!("Welcome to JapLearn");
iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) iced::application(ScreenState::boot, ScreenState::update, ScreenState::view)
.window(window_settings()) .window(window_settings())
+23 -16
View File
@@ -7,57 +7,64 @@ use std::fmt::{write, Display, Formatter};
use std::num::ParseIntError; use std::num::ParseIntError;
use std::str::FromStr; use std::str::FromStr;
macro_rules! id_impl {
($id_type:ty, $name:ident, $invalid:ident) => {
#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy, Ord, PartialOrd, Default)] #[derive(Clone, PartialEq, Eq, Debug, Hash, Copy, Ord, PartialOrd, Default)]
pub struct Id(pub(crate) u32); pub struct $name(pub(crate) $id_type);
pub const INVALID_ID: Id = Id(0); pub const $invalid: $name = $name(0);
impl FromStr for Id { impl FromStr for $name {
type Err = ParseIntError; type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Id(s.parse::<u32>()?)) Ok($name(s.parse::<$id_type>()?))
} }
} }
impl From<u32> for Id { impl From<$id_type> for $name {
fn from(value: u32) -> Self { fn from(value: $id_type) -> Self {
Id(value) $name(value)
} }
} }
impl From<Id> for u32 { impl From<$name> for $id_type {
fn from(value: Id) -> Self { fn from(value: $name) -> Self {
value.0 value.0
} }
} }
impl Display for Id { impl Display for $name {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
impl FromSql for Id { impl FromSql for $name {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value { match value {
ValueRef::Integer(i) => Ok(Id(i as u32)), ValueRef::Integer(i) => Ok($name(i as $id_type)),
_ => Err(FromSqlError::InvalidType), _ => Err(FromSqlError::InvalidType),
} }
} }
} }
impl ToSql for Id { impl ToSql for $name {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0 as i64)) Ok(ToSqlOutput::from(self.0 as i64))
} }
} }
impl From<Id> for String { impl From<$name> for String {
fn from(value: Id) -> Self { fn from(value: $name) -> Self {
value.0.to_string() value.0.to_string()
} }
} }
impl Id { impl $name {
pub(crate) fn is_valid(self) -> bool { pub(crate) fn is_valid(self) -> bool {
self.0 != 0 self.0 != 0
} }
} }
}
}
id_impl!(u32, Id, INVALID_ID);
id_impl!(u16, SmallId, INVALID_SMALL_ID);
+24 -3
View File
@@ -7,18 +7,18 @@ use log::info;
use rayon::prelude::*; use rayon::prelude::*;
use rhai::{Engine, Scope}; use rhai::{Engine, Scope};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Instant; use std::time::Instant;
use litemap::LiteMap;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct WordData { pub struct WordData {
pub id: Id, pub id: Id,
pub group_id: Id,
pub key: String, pub key: String,
pub value: String, pub value: String,
pub tags: String, pub tags: String,
pub additional: HashMap<String, String>, pub additional: LiteMap<String, String>,
pub group_id: Id,
} }
impl EntityData for WordData { impl EntityData for WordData {
@@ -37,6 +37,16 @@ pub struct WordGroup {
pub name: String, pub name: String,
} }
impl EntityData for WordGroup {
fn id(&self) -> Id {
self.id
}
fn set_id(&mut self, id: Id) {
self.id = id;
}
}
impl WordData { impl WordData {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@@ -155,6 +165,17 @@ pub struct CardState {
pub fsrs_memory: Option<MemoryState>, pub fsrs_memory: Option<MemoryState>,
pub fsrs_open_day: NaiveDate, pub fsrs_open_day: NaiveDate,
} }
impl EntityData for CardState {
fn id(&self) -> Id {
self.id
}
fn set_id(&mut self, id: Id) {
self.id = id;
}
}
impl CardState { impl CardState {
pub fn update(&mut self, status: WordOpenMode) { pub fn update(&mut self, status: WordOpenMode) {
match status { match status {
+7 -2
View File
@@ -65,8 +65,13 @@ 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;", []) .query_one("PRAGMA journal_mode = WAL;", [], |row| {
.expect("Failed to set PRAGMA"); println!("{:?}", row);
Ok(())
})
.unwrap_or_else(|e| {
println!("{}", e);
});
connection connection
.execute("PRAGMA synchronous = NORMAL;", []) .execute("PRAGMA synchronous = NORMAL;", [])
.expect("Failed to set PRAGMA"); .expect("Failed to set PRAGMA");
+90 -2
View File
@@ -1,8 +1,96 @@
use crate::data::entities::{WordData, WordGroup}; use crate::data::entities::{WordData, WordGroup};
use crate::data_provider::TableAccessQuerySet;
use crate::notification::NotificationData::{DbLoadError, DbSaveError}; use crate::notification::NotificationData::{DbLoadError, DbSaveError};
use crate::notification::{MaybeError, NotificationData}; use crate::notification::{MaybeError, NotificationData};
use rusqlite::{Connection, params}; use rusqlite::{Connection, Error, Params, Row, params};
use std::collections::HashMap; use std::collections::HashMap;
use litemap::LiteMap;
pub struct WordQuerySet;
impl TableAccessQuerySet<WordData> for WordQuerySet {
fn load() -> &'static str {
"SELECT id, key, value, tags, more, group_id FROM words"
}
fn save() -> &'static str {
"UPDATE words SET key = ?1, value = ?2, tags = ?3, more = ?4 WHERE id = ?5"
}
fn add() -> &'static str {
"INSERT INTO words (key, value, tags, more, group_id) VALUES (?1, ?2, ?3, ?4, ?5)"
}
fn name() -> &'static str {
"words"
}
fn from_sql(row: &Row) -> Result<WordData, Error> {
let additional: String = row.get(4)?;
Ok(WordData {
id: row.get(0)?,
key: row.get(1)?,
value: row.get(2)?,
tags: row.get(3)?,
additional: serde_json::from_str(&additional)
.unwrap_or_default(),
group_id: row.get(5)?,
})
}
fn insert_params(entity: &WordData) -> impl Params {
(
&entity.key,
&entity.value,
&entity.tags,
serde_json::to_string(&entity.additional).unwrap_or_default(),
entity.group_id,
)
}
fn update_params(entity: &WordData) -> impl Params {
(
&entity.key,
&entity.value,
&entity.tags,
serde_json::to_string(&entity.additional).unwrap_or_default(),
entity.id,
)
}
}
pub struct WordGroupQuerySet;
impl TableAccessQuerySet<WordGroup> for WordGroupQuerySet {
fn load() -> &'static str {
"SELECT id, name FROM word_groups"
}
fn save() -> &'static str {
"UPDATE word_groups SET name = ?1 WHERE id = ?2"
}
fn add() -> &'static str {
"INSERT INTO word_groups (name) VALUES (?1)"
}
fn name() -> &'static str {
"word_groups"
}
fn from_sql(row: &Row) -> Result<WordGroup, Error> {
Ok(WordGroup {
id: row.get(0)?,
name: row.get(1)?,
})
}
fn insert_params(entity: &WordGroup) -> impl Params {
(&entity.name,)
}
fn update_params(entity: &WordGroup) -> impl Params {
(&entity.name, entity.id)
}
}
pub fn add_word(word: &mut WordData, connection: &Connection) -> MaybeError { pub fn add_word(word: &mut WordData, connection: &Connection) -> MaybeError {
let index : u32 = connection let index : u32 = connection
@@ -99,7 +187,7 @@ pub fn load_words(connection: &Connection) -> Result<Vec<WordData>, Notification
key: row.get(1)?, key: row.get(1)?,
value: row.get(2)?, value: row.get(2)?,
tags: row.get(3)?, tags: row.get(3)?,
additional: serde_json::from_str::<HashMap<String, String>>(&additional) additional: serde_json::from_str(&additional)
.unwrap_or_default(), .unwrap_or_default(),
group_id: row.get(5)?, group_id: row.get(5)?,
}) })
+2 -2
View File
@@ -139,7 +139,7 @@ impl NotificationManager {
fn draw_data<'a>(data: &NotificationData) -> Element<'a, NotificationMessage> { fn draw_data<'a>(data: &NotificationData) -> Element<'a, NotificationMessage> {
match data { match data {
NotificationData::DataLoaded => { NotificationData::DataLoaded => {
text!("Данные успешно скачаны") text!("Данные успешно загружены на сервер")
} }
NotificationData::ImportError => { NotificationData::ImportError => {
text!("Ошибка при импорте данных из Anki") text!("Ошибка при импорте данных из Anki")
@@ -154,7 +154,7 @@ impl NotificationManager {
text!("Не получается подключиться к VoiceVox") text!("Не получается подключиться к VoiceVox")
} }
NotificationData::PermissionDenied => { NotificationData::PermissionDenied => {
text!("У приложение недостаточно прав") text!("У приложение недостаточно прав для доступа в файлам")
} }
NotificationData::WebApiUnavailable => { NotificationData::WebApiUnavailable => {
text!("Не получается подключиться к Api") text!("Не получается подключиться к Api")