feat: add catalog persistance

This commit is contained in:
2025-09-29 16:41:44 +02:00
parent b063ecaf9c
commit 90fa4119dd
9 changed files with 104 additions and 4 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
01.workspace/target 01.workspace/target
01.workspace/heave/rustybudger.sqlite3

View File

@@ -1 +1,2 @@
pub mod sqlite_init_db; pub mod sqlite_init_db;
pub mod sqlite_persist_catalog;

View File

@@ -8,13 +8,14 @@ pub fn run(path: &path::Path) {
class TEXT NOT NULL class TEXT NOT NULL
); );
CREATE TABLE IF NOT EXISTS attribute ( CREATE TABLE IF NOT EXISTS attribute (
id TEXT PRIMARY KEY, id TEXT,
entity_id TEXT,
value_int INTEGER, value_int INTEGER,
value_uint INTEGER, value_uint INTEGER,
value_real REAL, value_real REAL,
value_text TEXT, value_text TEXT,
value_bool BOOL, value_bool BOOL,
entity_id TEXT, CONSTRAINT pk_id PRIMARY KEY (id, entity_id),
CONSTRAINT fk_entity_id FOREIGN KEY (entity_id) REFERENCES entity (id) ON DELETE CASCADE ON UPDATE CASCADE CONSTRAINT fk_entity_id FOREIGN KEY (entity_id) REFERENCES entity (id) ON DELETE CASCADE ON UPDATE CASCADE
); );
"#; "#;

View File

@@ -0,0 +1,57 @@
use crate::*;
use rusqlite::*;
fn column(value: &Value) -> &'static str {
match value {
Value::SignedInt(_) => "value_int",
Value::UnsignedInt(_) => "value_uint",
Value::Real(_) => "value_real",
Value::Text(_) => "value_text",
Value::Bool(_) => "value_bool",
}
}
pub fn run(path: &path::Path, catalog: &Catalog) {
let mut connection = Connection::open(path).unwrap();
let delete_entity_statement = r#"
DELETE FROM entity
WHERE entity.id = ?1;
"#;
let insert_entity_statement = r#"
INSERT INTO entity (id, class)
VALUES (?1, ?2);
"#;
let insert_attribute_statement_template = r#"
INSERT INTO attribute (id, entity_id, {column})
VALUES (?1, ?2, ?3);
"#;
let transaction = connection.transaction().unwrap();
for (_key, entity) in catalog.items.iter() {
let _ = transaction.execute(delete_entity_statement, [&entity.id]);
let _ = transaction.execute(insert_entity_statement, (&entity.id, &entity.class));
for (_key, attribute) in entity.attributes.iter() {
let insert_attribute_statement =
insert_attribute_statement_template.replace("{column}", column(&attribute.value));
let _ = transaction.execute(
&insert_attribute_statement,
(&attribute.id, &entity.id, &attribute.value.to_string()),
);
}
}
let _ = transaction.commit();
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_call() {
let tempfile = tempfile::NamedTempFile::new().unwrap();
let path = tempfile.path();
let entity = Entity::new("test");
let mut catalog = Catalog::new("");
catalog.insert(entity);
sqlite::init::db(path);
run(path, &catalog);
}
}

View File

@@ -20,4 +20,7 @@ mod sqlite {
pub mod init { pub mod init {
pub use crate::fun::sqlite_init_db::run as db; pub use crate::fun::sqlite_init_db::run as db;
} }
pub mod persist {
pub use crate::fun::sqlite_persist_catalog::run as catalog;
}
} }

View File

@@ -3,11 +3,12 @@ use crate::*;
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
pub struct O { pub struct O {
path: String, path: String,
items: std::collections::HashMap<String, Entity>, pub(crate) items: std::collections::HashMap<String, Entity>,
} }
impl O { impl O {
pub fn new(path: &str) -> Self { pub fn new(path: &str) -> Self {
sqlite::init::db(path::Path::new(path));
Self { Self {
path: String::from(path), path: String::from(path),
..O::default() ..O::default()
@@ -34,6 +35,10 @@ impl O {
let entity = self.items.get(id); let entity = self.items.get(id);
entity.map(|e| T::from_eav(e.clone())) entity.map(|e| T::from_eav(e.clone()))
} }
pub fn persist(&self) {
let path = path::Path::new(&self.path);
sqlite::persist::catalog(path, self);
}
} }
// impl std::fmt::Display for O { // impl std::fmt::Display for O {

View File

@@ -7,6 +7,19 @@ pub struct O {
pub attributes: std::collections::HashMap<String, Attribute>, pub attributes: std::collections::HashMap<String, Attribute>,
} }
impl EAV for Entity {}
impl ToEAV for Entity {
fn to_eav(self) -> Entity {
self
}
}
impl FromEAV for Entity {
fn from_eav(entity: Entity) -> Self {
entity
}
}
impl O { impl O {
pub fn new(class: &str) -> Self { pub fn new(class: &str) -> Self {
Self { Self {

View File

@@ -8,3 +8,16 @@ pub enum E {
Text(String), Text(String),
UnsignedInt(u64), UnsignedInt(u64),
} }
impl std::fmt::Display for E {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let string_value = match self {
Value::Bool(value) => value.to_string(),
Value::Real(value) => value.to_string(),
Value::SignedInt(value) => value.to_string(),
Value::UnsignedInt(value) => value.to_string(),
Value::Text(value) => value.clone(),
};
write!(f, "{}", string_value)
}
}

View File

@@ -87,7 +87,12 @@ mod tests {
} }
#[test] #[test]
pub fn check_001() { pub fn check_001() {
let mut catalog = Catalog::new(""); let file = "./rustybudger.sqlite3";
// clean filesystem
if let Ok(true) = std::fs::exists(file) {
let _ = std::fs::remove_file(file);
}
let mut catalog = Catalog::new(file);
let operation_01 = Operation { let operation_01 = Operation {
id: short_uuid::short!().to_string(), id: short_uuid::short!().to_string(),
date: 20250929, date: 20250929,
@@ -115,5 +120,6 @@ mod tests {
catalog.insert_many(operations); catalog.insert_many(operations);
catalog.insert_many(categories); catalog.insert_many(categories);
catalog.insert_many(relations); catalog.insert_many(relations);
catalog.persist();
} }
} }