1use std::cell::RefCell;
26use std::collections::{BTreeMap, HashMap};
27use std::fmt;
28use std::rc::Rc;
29
30use ravenrs_app::{
31 AttachmentStore, Change, ChangeSet, Diff, EntityInfo, FieldInfo, FieldKind, ModelError, ModelStore,
32 Object, ObjectSpace, Query, SpaceError, SpaceEvent, SpaceWatcher, Value, key_of, key_text, run_query,
33};
34use ravenrs_app_i18n::{Language, Texts};
35use ravenrs_criteria::{DateTime, Dialect, Environment, SqlParam, to_sql};
36use rusqlite::types::{Value as SqlValue, ValueRef};
37use rusqlite::{Connection, ErrorCode, OptionalExtension, params_from_iter};
38
39type Hook = Rc<dyn Fn(&mut Object)>;
40
41#[derive(Clone)]
43pub struct TableMap {
44 table: String,
45 columns: BTreeMap<String, String>,
46 computed: BTreeMap<String, String>,
47 before_save: Option<Hook>,
48 after_load: Option<Hook>,
49}
50
51impl fmt::Debug for TableMap {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_struct("TableMap").field("table", &self.table).finish_non_exhaustive()
54 }
55}
56
57impl TableMap {
58 pub fn new(table: impl Into<String>) -> Self {
60 Self {
61 table: table.into(),
62 columns: BTreeMap::new(),
63 computed: BTreeMap::new(),
64 before_save: None,
65 after_load: None,
66 }
67 }
68
69 pub fn column(mut self, field: impl Into<String>, column: impl Into<String>) -> Self {
71 self.columns.insert(field.into(), column.into());
72 self
73 }
74
75 pub fn computed(mut self, field: impl Into<String>, sql: impl Into<String>) -> Self {
78 self.computed.insert(field.into(), sql.into());
79 self
80 }
81
82 pub fn before_save(mut self, hook: impl Fn(&mut Object) + 'static) -> Self {
84 self.before_save = Some(Rc::new(hook));
85 self
86 }
87
88 pub fn after_load(mut self, hook: impl Fn(&mut Object) + 'static) -> Self {
90 self.after_load = Some(Rc::new(hook));
91 self
92 }
93
94 fn column_of<'a>(&'a self, field: &'a str) -> &'a str {
95 self.columns.get(field).map_or(field, String::as_str)
96 }
97}
98
99#[derive(Clone, Debug)]
101#[non_exhaustive]
102pub struct SqlWords {
103 pub unique: String,
105 pub in_use: String,
107 pub rule: String,
109 pub failed: String,
111}
112
113impl Default for SqlWords {
114 fn default() -> Self {
115 Self::from_texts(&Texts::new(Language::English))
116 }
117}
118
119impl SqlWords {
120 pub fn from_texts(texts: &Texts) -> Self {
122 Self {
123 unique: texts.get("sqlite.unique"),
124 in_use: texts.get("sqlite.in_use"),
125 rule: texts.get("sqlite.rule"),
126 failed: texts.get("sqlite.failed"),
127 }
128 }
129
130 pub fn pt_br() -> Self {
132 Self::from_texts(&Texts::new(Language::BrazilianPortuguese))
133 }
134}
135
136fn quoted(name: &str) -> String {
137 format!("\"{}\"", name.replace('"', "\"\""))
138}
139
140fn sql_type(kind: &FieldKind) -> &'static str {
141 match kind {
142 FieldKind::Integer | FieldKind::Money | FieldKind::Boolean => "INTEGER",
143 FieldKind::Decimal(_) => "REAL",
144 _ => "TEXT",
145 }
146}
147
148const MIGRATIONS: &str = "ravenrs_app_migrations";
150const SETTINGS: &str = "ravenrs_app_settings";
152const FILES: &str = "ravenrs_app_files";
154
155struct Column {
157 name: String,
158 kind: String,
159 required: bool,
160 key: bool,
161}
162
163fn columns_of(connection: &Connection, table: &str) -> rusqlite::Result<Vec<Column>> {
164 let mut statement = connection.prepare(&format!("PRAGMA table_info({})", quoted(table)))?;
165 let rows = statement.query_map([], |row| {
166 let default: Option<String> = row.get(4)?;
167 let not_null: bool = row.get(3)?;
168 Ok(Column {
169 name: row.get(1)?,
170 kind: row.get::<_, String>(2)?.to_uppercase(),
171 required: not_null && default.is_none(),
172 key: row.get::<_, i64>(5)? > 0,
173 })
174 })?;
175 rows.collect()
176}
177
178fn entity_name(table: &str) -> String {
180 table
181 .split('_')
182 .map(|part| {
183 let mut letters = part.chars();
184 letters.next().map_or_else(String::new, |first| first.to_uppercase().chain(letters).collect())
185 })
186 .collect()
187}
188
189fn kind_of(declared: &str) -> Option<FieldKind> {
191 if declared.contains("BLOB") {
192 None
193 } else if declared.contains("INT") {
194 Some(FieldKind::Integer)
195 } else if ["REAL", "FLOA", "DOUB", "NUMERIC", "DECIMAL"].iter().any(|name| declared.contains(name)) {
196 Some(FieldKind::Decimal(2))
197 } else if declared.contains("DATETIME") || declared.contains("TIMESTAMP") {
198 Some(FieldKind::DateTime)
199 } else if declared.contains("DATE") {
200 Some(FieldKind::Date)
201 } else if declared.contains("BOOL") {
202 Some(FieldKind::Boolean)
203 } else {
204 Some(FieldKind::Text)
205 }
206}
207
208pub fn entities_from_schema(connection: &Connection) -> Result<Vec<(EntityInfo, TableMap)>, ModelError> {
213 let wrong = |e: rusqlite::Error| ModelError(e.to_string());
214 let mut statement = connection
215 .prepare(
216 "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
217 )
218 .map_err(wrong)?;
219 let tables: Vec<String> =
220 statement.query_map([], |row| row.get(0)).and_then(Iterator::collect).map_err(wrong)?;
221 let mut found = Vec::new();
222 for table in tables.iter().filter(|table| ![MIGRATIONS, SETTINGS, FILES].contains(&table.as_str())) {
223 let columns = columns_of(connection, table).map_err(wrong)?;
224 if columns.iter().filter(|column| column.key).count() != 1 {
225 continue;
226 }
227 let mut statement =
228 connection.prepare(&format!("PRAGMA foreign_key_list({})", quoted(table))).map_err(wrong)?;
229 let foreign: Vec<(String, String)> = statement
230 .query_map([], |row| Ok((row.get(3)?, row.get(2)?)))
231 .and_then(Iterator::collect)
232 .map_err(wrong)?;
233 let names_a_record = columns
234 .iter()
235 .find(|c| ["name", "title", "description"].contains(&c.name.as_str()))
236 .map(|c| c.name.clone());
237 let mut entity = EntityInfo::new(entity_name(table));
238 for column in &columns {
239 let target = foreign.iter().find(|(from, _)| *from == column.name).map(|(_, to)| to);
240 let kind = match target {
241 Some(to) => FieldKind::Reference(entity_name(to)),
242 None => match kind_of(&column.kind) {
243 Some(kind) => kind,
244 None => continue,
245 },
246 };
247 let number = matches!(kind, FieldKind::Integer);
248 let field = FieldInfo::new(&column.name, kind);
249 let field = match (column.key, number) {
250 (true, true) => field.key(),
251 (true, false) => field.typed_key(),
252 _ => field,
253 };
254 let field = if names_a_record.as_deref() == Some(column.name.as_str()) {
255 field.default_property()
256 } else {
257 field
258 };
259 entity = entity.field(field);
260 if column.required && !column.key {
261 entity = entity.rule(ravenrs_app::Rule::required(&column.name));
262 }
263 }
264 found.push((entity, TableMap::new(table)));
265 }
266 Ok(found)
267}
268
269#[derive(Clone, Debug)]
271pub struct SqliteModelStore {
272 connection: Rc<Connection>,
273 key: String,
274}
275
276impl SqliteModelStore {
277 pub fn new(connection: Rc<Connection>, key: impl Into<String>) -> Result<Self, ModelError> {
279 connection
280 .execute_batch(&format!(
281 "CREATE TABLE IF NOT EXISTS {SETTINGS} (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
282 ))
283 .map_err(|e| ModelError(e.to_string()))?;
284 Ok(Self { connection, key: key.into() })
285 }
286}
287
288impl ModelStore for SqliteModelStore {
289 fn load(&self) -> Result<Option<Diff>, ModelError> {
290 let sql = format!("SELECT value FROM {SETTINGS} WHERE key = ?1");
291 let text: Option<String> = self
292 .connection
293 .query_row(&sql, [&self.key], |row| row.get(0))
294 .optional()
295 .map_err(|e| ModelError(e.to_string()))?;
296 text.map(|text| Diff::parse(&text)).transpose()
297 }
298
299 fn save(&self, difference: &Diff) -> Result<(), ModelError> {
300 let sql = format!("INSERT OR REPLACE INTO {SETTINGS} (key, value) VALUES (?1, ?2)");
301 self.connection
302 .execute(&sql, [self.key.as_str(), difference.to_json().as_str()])
303 .map(|_| ())
304 .map_err(|e| ModelError(e.to_string()))
305 }
306}
307
308#[derive(Clone, Debug)]
310pub struct SqliteAttachmentStore {
311 connection: Rc<Connection>,
312}
313
314impl SqliteAttachmentStore {
315 pub fn new(connection: Rc<Connection>) -> Result<Self, ModelError> {
317 connection
318 .execute_batch(&format!(
319 "CREATE TABLE IF NOT EXISTS {FILES} (id INTEGER PRIMARY KEY, name TEXT NOT NULL, content BLOB NOT NULL)"
320 ))
321 .map_err(|e| ModelError(e.to_string()))?;
322 Ok(Self { connection })
323 }
324}
325
326impl AttachmentStore for SqliteAttachmentStore {
327 fn put(&self, name: &str, bytes: &[u8]) -> Result<String, String> {
328 let sql = format!("INSERT INTO {FILES} (name, content) VALUES (?1, ?2)");
329 self.connection.execute(&sql, rusqlite::params![name, bytes]).map_err(|e| e.to_string())?;
330 Ok(self.connection.last_insert_rowid().to_string())
331 }
332
333 fn get(&self, key: &str) -> Result<Option<Vec<u8>>, String> {
334 let sql = format!("SELECT content FROM {FILES} WHERE id = ?1");
335 self.connection.query_row(&sql, [key], |row| row.get(0)).optional().map_err(|e| e.to_string())
336 }
337
338 fn remove(&self, key: &str) -> Result<(), String> {
339 let sql = format!("DELETE FROM {FILES} WHERE id = ?1");
340 self.connection.execute(&sql, [key]).map(|_| ()).map_err(|e| e.to_string())
341 }
342}
343
344struct Entity {
345 info: EntityInfo,
346 map: TableMap,
347}
348
349impl Entity {
350 fn stored(&self) -> impl Iterator<Item = &FieldInfo> {
352 self.info.fields.iter().filter(|f| {
353 f.calculated.is_none()
354 && !matches!(f.kind, FieldKind::Collection(_))
355 && !self.map.computed.contains_key(&f.name)
356 })
357 }
358
359 fn select(&self) -> String {
361 let mut columns: Vec<String> = self
362 .stored()
363 .map(|f| format!("{} AS {}", quoted(self.map.column_of(&f.name)), quoted(&f.name)))
364 .collect();
365 columns.extend(self.map.computed.iter().map(|(field, sql)| format!("({sql}) AS {}", quoted(field))));
366 format!(
367 "SELECT {} FROM {} AS {}",
368 columns.join(", "),
369 quoted(&self.map.table),
370 quoted(&self.map.table)
371 )
372 }
373
374 fn read(&self, row: &rusqlite::Row<'_>, names: &[String]) -> rusqlite::Result<Object> {
375 let mut values = BTreeMap::new();
376 for (at, name) in names.iter().enumerate() {
377 let kind = self.info.field_named(name).map(|f| &f.kind);
378 let value = match (row.get_ref(at)?, kind) {
379 (ValueRef::Null, _) => Value::Null,
380 (ValueRef::Integer(n), Some(FieldKind::Boolean)) => Value::Bool(n != 0),
381 (ValueRef::Integer(n), _) => Value::Number(n as f64),
382 (ValueRef::Real(n), _) => Value::Number(n),
383 (ValueRef::Text(bytes), kind) => {
384 let text = String::from_utf8_lossy(bytes).into_owned();
385 match kind {
386 Some(FieldKind::Date | FieldKind::DateTime) => {
387 DateTime::parse(&text).map_or(Value::Text(text), Value::Date)
388 }
389 _ => Value::Text(text),
390 }
391 }
392 (ValueRef::Blob(_), _) => Value::Null,
393 };
394 values.insert(name.clone(), value);
395 }
396 Ok(Object::from_values(&self.info.name, values))
397 }
398}
399
400fn bound(value: &Value) -> SqlValue {
401 match value {
402 Value::Bool(on) => SqlValue::Integer(i64::from(*on)),
403 Value::Number(n) if n.fract() == 0.0 && n.abs() < 9.0e15 => SqlValue::Integer(*n as i64),
404 Value::Number(n) => SqlValue::Real(*n),
405 Value::Text(text) => SqlValue::Text(text.clone()),
406 Value::Date(moment) => SqlValue::Text(moment.to_string()),
407 _ => SqlValue::Null,
408 }
409}
410
411struct Shared {
412 connection: Rc<Connection>,
413 entities: Vec<Entity>,
414 words: SqlWords,
415 next_keys: RefCell<HashMap<String, i64>>,
416}
417
418#[derive(Clone)]
421pub struct SqliteDatabase(Rc<Shared>);
422
423impl fmt::Debug for SqliteDatabase {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 f.debug_struct("SqliteDatabase").field("entities", &self.0.entities.len()).finish()
426 }
427}
428
429pub struct SqliteBuilder {
431 connection: Rc<Connection>,
432 entities: Vec<Entity>,
433 words: SqlWords,
434}
435
436impl fmt::Debug for SqliteBuilder {
437 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438 f.debug_struct("SqliteBuilder").field("entities", &self.entities.len()).finish()
439 }
440}
441
442impl SqliteBuilder {
443 pub fn words(mut self, words: SqlWords) -> Self {
445 self.words = words;
446 self
447 }
448
449 fn checked(entity: &EntityInfo) -> Result<(), ModelError> {
450 entity.check()
451 }
452
453 pub fn mapped(mut self, entity: EntityInfo, map: TableMap) -> Result<Self, ModelError> {
455 Self::checked(&entity)?;
456 self.entities.push(Entity { info: entity, map });
457 Ok(self)
458 }
459
460 pub fn managed(self, entity: EntityInfo, map: TableMap) -> Result<Self, ModelError> {
462 let mine = self.mapped(entity, map)?;
463 let Some(added) = mine.entities.last() else { return Ok(mine) };
464 let columns: Vec<String> = added
465 .stored()
466 .map(|field| {
467 let column = quoted(added.map.column_of(&field.name));
468 let key = if field.is_key { " PRIMARY KEY" } else { "" };
469 format!("{column} {}{key}", sql_type(&field.kind))
470 })
471 .collect();
472 let sql = format!("CREATE TABLE IF NOT EXISTS {} ({})", quoted(&added.map.table), columns.join(", "));
473 mine.connection.execute(&sql, []).map_err(|e| ModelError(e.to_string()))?;
474 let wrong = |e: rusqlite::Error| ModelError(e.to_string());
476 let has = columns_of(&mine.connection, &added.map.table).map_err(wrong)?;
477 for field in added.stored() {
478 let column = added.map.column_of(&field.name);
479 if !has.iter().any(|found| found.name == column) {
480 let add = format!(
481 "ALTER TABLE {} ADD COLUMN {} {}",
482 quoted(&added.map.table),
483 quoted(column),
484 sql_type(&field.kind)
485 );
486 mine.connection.execute(&add, []).map_err(wrong)?;
487 }
488 }
489 Ok(mine)
490 }
491
492 pub fn migration(self, id: &str, sql: &str) -> Result<Self, ModelError> {
495 let wrong = |e: rusqlite::Error| ModelError(format!("the migration {id}: {e}"));
496 let connection = &self.connection;
497 connection
498 .execute_batch(&format!("CREATE TABLE IF NOT EXISTS {MIGRATIONS} (id TEXT PRIMARY KEY)"))
499 .map_err(wrong)?;
500 let done = connection
501 .prepare(&format!("SELECT 1 FROM {MIGRATIONS} WHERE id = ?1"))
502 .and_then(|mut statement| statement.exists([id]))
503 .map_err(wrong)?;
504 if !done {
505 let transaction = connection.unchecked_transaction().map_err(wrong)?;
506 connection.execute_batch(sql).map_err(wrong)?;
507 connection.execute(&format!("INSERT INTO {MIGRATIONS} (id) VALUES (?1)"), [id]).map_err(wrong)?;
508 transaction.commit().map_err(wrong)?;
509 }
510 Ok(self)
511 }
512
513 pub fn build(self) -> SqliteDatabase {
515 SqliteDatabase(Rc::new(Shared {
516 connection: self.connection,
517 entities: self.entities,
518 words: self.words,
519 next_keys: RefCell::default(),
520 }))
521 }
522}
523
524impl SqliteDatabase {
525 pub fn builder(connection: Rc<Connection>) -> SqliteBuilder {
527 SqliteBuilder { connection, entities: Vec::new(), words: SqlWords::default() }
528 }
529
530 pub fn space(&self) -> SqliteObjectSpace {
532 SqliteObjectSpace {
533 shared: self.0.clone(),
534 environment: ravenrs_app::environment(),
535 changes: ChangeSet::default(),
536 watchers: Vec::new(),
537 }
538 }
539
540 pub fn entities(&self) -> Vec<EntityInfo> {
542 self.0.entities.iter().map(|entity| entity.info.clone()).collect()
543 }
544}
545
546pub struct SqliteObjectSpace {
548 shared: Rc<Shared>,
549 environment: Environment,
550 changes: ChangeSet,
551 watchers: Vec<SpaceWatcher>,
552}
553
554impl fmt::Debug for SqliteObjectSpace {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 f.debug_struct("SqliteObjectSpace").finish_non_exhaustive()
557 }
558}
559
560impl SqliteObjectSpace {
561 fn entity(&self, name: &str) -> Result<&Entity, SpaceError> {
562 self.shared
563 .entities
564 .iter()
565 .find(|entity| entity.info.name == name)
566 .ok_or_else(|| SpaceError::UnknownEntity(name.to_owned()))
567 }
568
569 fn refused(&self, entity: &Entity, error: &rusqlite::Error) -> SpaceError {
571 let words = &self.shared.words;
572 let text = error.to_string();
573 if error.sqlite_error_code() != Some(ErrorCode::ConstraintViolation) {
574 return SpaceError::Storage(words.failed.replace("{0}", &text));
575 }
576 if text.contains("FOREIGN KEY") {
577 return SpaceError::Refused(words.in_use.clone());
578 }
579 if text.contains("UNIQUE") {
580 let caption = text
582 .rsplit(": ")
583 .next()
584 .and_then(|columns| columns.split(", ").next())
585 .and_then(|column| column.rsplit('.').next())
586 .and_then(|column| {
587 entity.info.fields.iter().find(|f| entity.map.column_of(&f.name) == column)
588 })
589 .map_or_else(String::new, |field| field.caption.clone());
590 return SpaceError::Refused(words.unique.replace("{0}", &caption));
591 }
592 SpaceError::Refused(words.rule.clone())
593 }
594
595 fn select(
596 &self,
597 entity: &Entity,
598 tail: &str,
599 parameters: Vec<SqlValue>,
600 ) -> Result<Vec<Object>, SpaceError> {
601 self.select_columns(entity, "*", tail, parameters)
602 }
603
604 fn select_columns(
605 &self,
606 entity: &Entity,
607 columns: &str,
608 tail: &str,
609 parameters: Vec<SqlValue>,
610 ) -> Result<Vec<Object>, SpaceError> {
611 let sql = format!("SELECT {columns} FROM ({}){tail}", entity.select());
612 let storage = |e: rusqlite::Error| self.refused(entity, &e);
613 let mut statement = self.shared.connection.prepare_cached(&sql).map_err(storage)?;
614 let names: Vec<String> = statement.column_names().map(str::to_owned).collect();
615 let rows = statement
616 .query_map(params_from_iter(parameters), |row| entity.read(row, &names))
617 .map_err(storage)?;
618 let mut found = Vec::new();
619 for row in rows {
620 let mut object = row.map_err(storage)?;
621 entity.info.compute(&mut object, &self.environment);
622 if let Some(hook) = &entity.map.after_load {
623 hook(&mut object);
624 }
625 found.push(object);
626 }
627 Ok(found)
628 }
629
630 fn exists(&self, entity: &Entity, key: &str) -> bool {
631 let Some(field) = entity.info.key_field() else { return false };
632 let sql = format!(
633 "SELECT 1 FROM {} WHERE {} = ?1",
634 quoted(&entity.map.table),
635 quoted(entity.map.column_of(&field.name))
636 );
637 let value = match field.kind {
638 FieldKind::Integer | FieldKind::Decimal(_) => key.parse().map_or(SqlValue::Null, SqlValue::Real),
639 _ => SqlValue::Text(key.to_owned()),
640 };
641 self.shared
642 .connection
643 .prepare_cached(&sql)
644 .and_then(|mut statement| statement.exists([value]))
645 .unwrap_or(false)
646 }
647
648 fn next_key(&self, entity: &Entity, field: &FieldInfo) -> Result<i64, SpaceError> {
649 let sql = format!(
650 "SELECT COALESCE(MAX({}), 0) + 1 FROM {}",
651 quoted(entity.map.column_of(&field.name)),
652 quoted(&entity.map.table)
653 );
654 let stored: i64 = self
655 .shared
656 .connection
657 .query_row(&sql, [], |row| row.get(0))
658 .map_err(|e| self.refused(entity, &e))?;
659 let mut keys = self.shared.next_keys.borrow_mut();
660 let next = keys.get(&entity.info.name).copied().unwrap_or(0).max(stored);
661 keys.insert(entity.info.name.clone(), next + 1);
662 Ok(next)
663 }
664
665 fn write(&self, entity: &Entity, change: &Change) -> Result<(), SpaceError> {
666 let failed = |e: rusqlite::Error| self.refused(entity, &e);
667 let table = quoted(&entity.map.table);
668 let key_field =
669 entity.info.key_field().ok_or_else(|| SpaceError::MissingKey(entity.info.name.clone()))?;
670 let key_column = quoted(entity.map.column_of(&key_field.name));
671 let connection = &self.shared.connection;
672 let prepared = |object: &Object| {
673 let mut object = object.clone();
674 if let Some(hook) = &entity.map.before_save {
675 hook(&mut object);
676 }
677 object
678 };
679 match change {
680 Change::New(object) => {
681 let object = prepared(object);
682 let stored: Vec<&FieldInfo> = entity.stored().collect();
683 let columns: Vec<String> =
684 stored.iter().map(|f| quoted(entity.map.column_of(&f.name))).collect();
685 let slots: Vec<String> = (1..=stored.len()).map(|at| format!("?{at}")).collect();
686 let sql =
687 format!("INSERT INTO {table} ({}) VALUES ({})", columns.join(", "), slots.join(", "));
688 let values = stored.iter().map(|f| bound(&object.get(&f.name)));
689 connection.execute(&sql, params_from_iter(values)).map_err(failed)?;
690 }
691 Change::Updated(object) => {
692 let object = prepared(object);
693 let stored: Vec<&FieldInfo> = entity.stored().filter(|f| !f.is_key).collect();
694 let sets: Vec<String> = stored
695 .iter()
696 .enumerate()
697 .map(|(at, f)| format!("{} = ?{}", quoted(entity.map.column_of(&f.name)), at + 1))
698 .collect();
699 let sql = format!(
700 "UPDATE {table} SET {} WHERE {key_column} = ?{}",
701 sets.join(", "),
702 stored.len() + 1
703 );
704 let mut values: Vec<SqlValue> = stored.iter().map(|f| bound(&object.get(&f.name))).collect();
705 values.push(bound(&object.get(&key_field.name)));
706 let changed = connection.execute(&sql, params_from_iter(values)).map_err(failed)?;
707 if changed == 0 {
708 return Err(SpaceError::Conflict(format!("a record of {} is gone", entity.info.name)));
709 }
710 }
711 Change::Deleted { key, .. } => {
712 let sql = format!("DELETE FROM {table} WHERE {key_column} = ?1");
713 connection.execute(&sql, [bound(key)]).map_err(failed)?;
714 }
715 _ => {}
716 }
717 Ok(())
718 }
719
720 fn tell(&mut self, event: SpaceEvent<'_>) -> Result<(), SpaceError> {
721 for watcher in &mut self.watchers {
722 watcher(event).map_err(SpaceError::Refused)?;
723 }
724 Ok(())
725 }
726}
727
728impl ObjectSpace for SqliteObjectSpace {
729 fn entities(&self) -> Vec<EntityInfo> {
730 self.shared.entities.iter().map(|entity| entity.info.clone()).collect()
731 }
732
733 fn query(&self, name: &str, query: &Query) -> Result<Vec<Object>, SpaceError> {
734 let entity = self.entity(name)?;
735 let translated = query.filter.as_ref().map(|filter| to_sql(filter, Dialect::Sqlite));
737 let in_database = !self.changes.touches(name)
738 && !entity.info.fields.iter().any(|f| f.calculated.is_some())
739 && !matches!(translated, Some(Err(_)));
740 if !in_database {
741 let rows = self.select(entity, "", Vec::new())?;
742 let mut by_key: BTreeMap<String, Object> =
743 rows.into_iter().filter_map(|o| key_of(&entity.info, &o).map(|k| (k, o))).collect();
744 self.changes.overlay(&entity.info, &mut by_key);
745 let mut objects: Vec<Object> = by_key.into_values().collect();
746 for object in &mut objects {
747 entity.info.compute(object, &self.environment);
748 }
749 return run_query(&entity.info, objects, query, &self.environment);
750 }
751 ravenrs_app::check_query(&entity.info, query)?;
752 let mut tail = String::new();
753 let mut parameters = Vec::new();
754 if let Some(Ok(sql)) = translated {
755 tail.push_str(" WHERE ");
756 tail.push_str(&sql.text);
757 for parameter in &sql.params {
758 match parameter {
759 SqlParam::Value(value) => parameters.push(bound(value)),
760 _ => {
761 return Err(SpaceError::BadQuery(
762 "a filter asks for a value it was not given".into(),
763 ));
764 }
765 }
766 }
767 }
768 let key = entity.info.key_field().map(|f| f.name.clone()).unwrap_or_default();
769 let mut order: Vec<String> = query
770 .sort
771 .iter()
772 .map(|(field, ascending)| {
773 format!("{} {}", quoted(field), if *ascending { "ASC" } else { "DESC" })
774 })
775 .collect();
776 order.push(quoted(&key));
777 tail.push_str(&format!(" ORDER BY {}", order.join(", ")));
778 if let Some(limit) = query.limit {
779 tail.push_str(&format!(" LIMIT {limit} OFFSET {}", query.offset));
780 } else if query.offset > 0 {
781 tail.push_str(&format!(" LIMIT -1 OFFSET {}", query.offset));
782 }
783 let columns = query.only.as_ref().map_or_else(
784 || "*".to_owned(),
785 |only| {
786 let mut names: Vec<&str> = only.iter().map(String::as_str).collect();
787 if !names.contains(&key.as_str()) {
788 names.push(&key);
789 }
790 names.iter().map(|name| quoted(name)).collect::<Vec<_>>().join(", ")
791 },
792 );
793 self.select_columns(entity, &columns, &tail, parameters)
794 }
795
796 fn find(&self, name: &str, key: &Value) -> Result<Option<Object>, SpaceError> {
797 let entity = self.entity(name)?;
798 let Some(text) = key_text(key) else { return Ok(None) };
799 let field = entity.info.key_field().ok_or_else(|| SpaceError::MissingKey(name.to_owned()))?;
800 if self.changes.touches(name) {
801 let mut by_key = BTreeMap::new();
802 let tail = format!(" WHERE {} = ?1", quoted(&field.name));
803 for object in self.select(entity, &tail, vec![bound(key)])? {
804 by_key.insert(text.clone(), object);
805 }
806 self.changes.overlay(&entity.info, &mut by_key);
807 return Ok(by_key.remove(&text).map(|mut object| {
808 entity.info.compute(&mut object, &self.environment);
809 object
810 }));
811 }
812 let tail = format!(" WHERE {} = ?1", quoted(&field.name));
813 Ok(self.select(entity, &tail, vec![bound(key)])?.into_iter().next())
814 }
815
816 fn new_object(&mut self, name: &str) -> Result<Object, SpaceError> {
817 let entity = self.entity(name)?;
818 let mut object = entity.info.new_object();
819 if let Some(field) = entity.info.key_field()
820 && matches!(field.kind, FieldKind::Integer)
821 && matches!(object.get(&field.name), Value::Null)
822 {
823 let next = self.next_key(entity, field)?;
824 object.set(&field.name, Value::Number(next as f64));
825 }
826 Ok(object)
827 }
828
829 fn save(&mut self, object: Object) -> Result<(), SpaceError> {
830 let entity = self.entity(object.entity())?;
831 let info = entity.info.clone();
832 let exists = key_of(&info, &object).is_some_and(|key| self.exists(entity, &key));
833 self.changes.save(&info, object, exists)
834 }
835
836 fn insert(&mut self, object: Object) -> Result<(), SpaceError> {
837 let entity = self.entity(object.entity())?;
838 let info = entity.info.clone();
839 let exists = key_of(&info, &object).is_some_and(|key| self.exists(entity, &key));
840 self.changes.insert(&info, object, exists)
841 }
842
843 fn delete(&mut self, name: &str, key: &Value) -> Result<(), SpaceError> {
844 let entity = self.entity(name)?;
845 let info = entity.info.clone();
846 let exists = key_text(key).is_some_and(|text| self.exists(entity, &text));
847 self.changes.delete(&info, key, exists);
848 Ok(())
849 }
850
851 fn changes(&self) -> Vec<Change> {
852 self.changes.list().to_vec()
853 }
854
855 fn commit(&mut self) -> Result<(), SpaceError> {
856 let changes = self.changes.list().to_vec();
857 self.tell(SpaceEvent::Committing(&changes))?;
858 let shared = self.shared.clone();
859 let transaction =
860 shared.connection.unchecked_transaction().map_err(|e| SpaceError::Storage(e.to_string()))?;
861 for change in &changes {
862 let name = match change {
863 Change::New(object) | Change::Updated(object) => object.entity(),
864 Change::Deleted { entity, .. } => entity.as_str(),
865 _ => continue,
866 };
867 let entity = self.entity(name)?;
868 if let Change::New(new) | Change::Updated(new) = change
869 && !entity.info.state_machines.is_empty()
870 {
871 let field = entity.info.key_field().ok_or_else(|| SpaceError::MissingKey(name.to_owned()))?;
872 let tail = format!(" WHERE {} = ?1", quoted(entity.map.column_of(&field.name)));
873 let stored = self.select(entity, &tail, vec![bound(&new.get(&field.name))])?;
874 ravenrs_app::check_transitions(&entity.info, stored.first(), new)?;
875 }
876 self.write(entity, change)?;
877 }
878 transaction.commit().map_err(|e| SpaceError::Storage(e.to_string()))?;
879 self.changes.clear();
880 let _ = self.tell(SpaceEvent::Committed(&changes));
881 Ok(())
882 }
883
884 fn rollback(&mut self) {
885 self.changes.clear();
886 let _ = self.tell(SpaceEvent::RolledBack);
887 }
888
889 fn on_event(&mut self, watcher: SpaceWatcher) {
890 self.watchers.push(watcher);
891 }
892}