Skip to main content

ravenrs_app_sqlite/
lib.rs

1//! Records in SQLite, as an [`ObjectSpace`].
2//!
3//! ```
4//! use std::rc::Rc;
5//!
6//! use ravenrs_app::{EntityInfo, FieldInfo, FieldKind, ObjectSpace, Query, Value};
7//! use ravenrs_app_sqlite::{SqliteDatabase, TableMap};
8//!
9//! let account = EntityInfo::new("Account")
10//!     .field(FieldInfo::new("id", FieldKind::Integer).key())
11//!     .field(FieldInfo::new("name", FieldKind::Text));
12//! let connection = Rc::new(rusqlite::Connection::open_in_memory()?);
13//! let database = SqliteDatabase::builder(connection).managed(account, TableMap::new("accounts"))?.build();
14//!
15//! let mut space = database.space();
16//! let mut object = space.new_object("Account")?;
17//! object.set("name", "Corrente");
18//! space.save(object)?;
19//! space.commit()?;
20//! let found = database.space().query("Account", &Query::all())?;
21//! assert_eq!(found[0].get("name"), Value::Text("Corrente".into()));
22//! # Ok::<(), Box<dyn std::error::Error>>(())
23//! ```
24
25use 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/// How an entity lands on a table.
42#[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    /// On this table, with each field in a column of its name.
59    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    /// This field is in a column of another name.
70    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    /// This field is not stored: the database works it out with this SQL expression over the
76    /// table, which is trusted as written and never comes from people.
77    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    /// Runs on a record just before it is written, to fit what the table asks for.
83    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    /// Runs on a record just after it is read.
89    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/// What to say when the database refuses something, where `{0}` is the field's caption.
100#[derive(Clone, Debug)]
101#[non_exhaustive]
102pub struct SqlWords {
103    /// A value that has to be unique is not.
104    pub unique: String,
105    /// Something else still points to the record.
106    pub in_use: String,
107    /// Any other rule of the table.
108    pub rule: String,
109    /// Anything else, where `{0}` is what SQLite said.
110    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    /// The texts of a catalog, in its language. What it does not have comes from English.
121    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    /// In Brazilian Portuguese.
131    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
148/// Where migrations that ran are written down.
149const MIGRATIONS: &str = "ravenrs_app_migrations";
150/// Where settings of the model are kept.
151const SETTINGS: &str = "ravenrs_app_settings";
152/// Where the content of attached files is kept.
153const FILES: &str = "ravenrs_app_files";
154
155/// A column of a table, as SQLite describes it.
156struct 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
178/// `order_items` as `OrderItems`.
179fn 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
189/// What a column holds, by the type SQLite was told; nothing for what a field cannot show.
190fn 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
208/// Entities from the tables a database already has, for an application over a database that came
209/// first: a field per column, a reference per foreign key, and a required field per column that
210/// asks for it. A table with no single key is left out, since a record could not be found. What
211/// comes out is a start: captions and rules are for the application to give.
212pub 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/// Keeps a layer of the model in the database, under a name, as for one person's settings.
270#[derive(Clone, Debug)]
271pub struct SqliteModelStore {
272    connection: Rc<Connection>,
273    key: String,
274}
275
276impl SqliteModelStore {
277    /// A store under this name, creating where it keeps things if needed.
278    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/// Keeps the content of attached files in the database, in a table apart from the records.
309#[derive(Clone, Debug)]
310pub struct SqliteAttachmentStore {
311    connection: Rc<Connection>,
312}
313
314impl SqliteAttachmentStore {
315    /// A store on this connection, creating where it keeps things if needed.
316    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    /// The fields that have a column of their own.
351    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    /// A select with every field under its own name, so filters and order by field work.
360    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/// Records in SQLite, on a connection that can be shared with the rest of an application. Any
419/// number of spaces share it.
420#[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
429/// Puts a database together, one entity at a time.
430pub 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    /// What to say when the database refuses something.
444    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    /// An entity on a table that exists, whose schema is not touched.
454    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    /// An entity on a table that is created if it is missing.
461    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        // A table that was already there gets the fields added since.
475        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    /// Runs this once in the life of the database, for what adding a column cannot do, such as
493    /// renaming one or moving data. A migration that fails leaves everything as it was.
494    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    /// Ready to read and write.
514    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    /// Starts one on this connection. See [`SqliteBuilder`].
526    pub fn builder(connection: Rc<Connection>) -> SqliteBuilder {
527        SqliteBuilder { connection, entities: Vec::new(), words: SqlWords::default() }
528    }
529
530    /// A new unit of work.
531    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    /// The entities it knows.
541    pub fn entities(&self) -> Vec<EntityInfo> {
542        self.0.entities.iter().map(|entity| entity.info.clone()).collect()
543    }
544}
545
546/// A unit of work over a [`SqliteDatabase`]. A commit is one transaction.
547pub 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    /// Says what the database refused, in words for people.
570    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            // "UNIQUE constraint failed: accounts.name", possibly of several columns.
581            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        // What waits for a commit, or what SQL cannot say, goes through memory.
736        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}