Skip to main content

ravenrs_app/
space.rs

1//! Reading and writing records through a unit of work.
2
3use std::cell::RefCell;
4use std::cmp::Ordering;
5use std::collections::BTreeMap;
6use std::fmt;
7use std::rc::Rc;
8
9use ravenrs_core::Storage;
10use ravenrs_criteria::{Criteria, DateTime, Environment, Scope, Value};
11
12use crate::{EntityInfo, FieldKind, ModelError, Object};
13
14/// Why a space could not do what it was asked.
15#[derive(Clone, Debug, PartialEq, Eq)]
16#[non_exhaustive]
17#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
18pub enum SpaceError {
19    /// There is no entity by this name.
20    UnknownEntity(String),
21    /// The filter or the sort does not fit the entity.
22    BadQuery(String),
23    /// The object has no key yet.
24    MissingKey(String),
25    /// Another record already has this key.
26    DuplicateKey {
27        /// The entity.
28        entity: String,
29        /// The key.
30        key: String,
31    },
32    /// The record is gone, or something else took it since this space read it.
33    Conflict(String),
34    /// Something watching the commit refused it, with its reason.
35    Refused(String),
36    /// The person signed in cannot do this.
37    Denied(String),
38    /// Storage failed.
39    Storage(String),
40}
41
42impl fmt::Display for SpaceError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::UnknownEntity(name) => write!(f, "there is no entity named {name}"),
46            Self::BadQuery(why)
47            | Self::Conflict(why)
48            | Self::Refused(why)
49            | Self::Denied(why)
50            | Self::Storage(why) => f.write_str(why),
51            Self::MissingKey(entity) => write!(f, "a record of {entity} has no key"),
52            Self::DuplicateKey { entity, key } => {
53                write!(f, "{entity} already has a record with the key {key}")
54            }
55        }
56    }
57}
58
59impl std::error::Error for SpaceError {}
60
61/// What to read: a filter, an order and a page.
62#[derive(Clone, Debug, Default)]
63#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
64pub struct Query {
65    /// Only the records this says true for.
66    pub filter: Option<Criteria>,
67    /// Fields to sort by, with true for ascending.
68    pub sort: Vec<(String, bool)>,
69    /// How many to skip.
70    pub offset: usize,
71    /// The most to return.
72    pub limit: Option<usize>,
73    /// Only these fields, with the key, for a list that shows few of many; none means every one.
74    pub only: Option<Vec<String>>,
75}
76
77#[cfg_attr(feature = "live", ravenrs_live::expose)]
78impl Query {
79    /// Everything, in key order.
80    pub fn all() -> Self {
81        Self::default()
82    }
83
84    /// Only the records this says true for.
85    pub fn filter(mut self, filter: Criteria) -> Self {
86        self.filter = Some(filter);
87        self
88    }
89
90    /// Then by this field.
91    pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
92        self.sort.push((field.into(), ascending));
93        self
94    }
95
96    /// Only these fields, with the key. What is read this way is not for writing back.
97    pub fn only(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
98        self.only = Some(fields.into_iter().map(Into::into).collect());
99        self
100    }
101
102    /// A page of these many, after skipping those.
103    pub fn page(mut self, offset: usize, limit: usize) -> Self {
104        self.offset = offset;
105        self.limit = Some(limit);
106        self
107    }
108}
109
110/// A change waiting for a commit.
111#[derive(Clone, Debug, PartialEq)]
112#[non_exhaustive]
113#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
114pub enum Change {
115    /// A new record.
116    New(Object),
117    /// A record that changed.
118    Updated(Object),
119    /// A record to take away, by its entity and key.
120    Deleted {
121        /// The entity.
122        entity: String,
123        /// The key.
124        key: Value,
125    },
126}
127
128/// What happens to a space, for whoever watches it.
129#[derive(Clone, Copy, Debug)]
130#[non_exhaustive]
131pub enum SpaceEvent<'a> {
132    /// A commit is about to happen. An error stops it, and nothing is kept.
133    Committing(&'a [Change]),
134    /// A commit happened.
135    Committed(&'a [Change]),
136    /// The changes were thrown away.
137    RolledBack,
138}
139
140/// Watches a space; see [`ObjectSpace::on_event`].
141pub type SpaceWatcher = Box<dyn FnMut(SpaceEvent<'_>) -> Result<(), String>>;
142
143/// A unit of work over records: what it reads includes its own changes, and nothing
144/// reaches storage until [`commit`](Self::commit). A view has one of these, so canceling a
145/// form throws away only that form's changes.
146pub trait ObjectSpace {
147    /// The entities it knows.
148    fn entities(&self) -> Vec<EntityInfo>;
149    /// Reads records.
150    fn query(&self, entity: &str, query: &Query) -> Result<Vec<Object>, SpaceError>;
151    /// Reads one record by its key.
152    fn find(&self, entity: &str, key: &Value) -> Result<Option<Object>, SpaceError>;
153    /// A record with what a new one starts with, kept only once [`save`](Self::save)d.
154    fn new_object(&mut self, entity: &str) -> Result<Object, SpaceError>;
155    /// Marks a record as new or changed, by its key.
156    fn save(&mut self, object: Object) -> Result<(), SpaceError>;
157    /// Marks a record as new, refusing a key already in use, as a form creating
158    /// a record has to: it must not take over the record with that key.
159    fn insert(&mut self, object: Object) -> Result<(), SpaceError>;
160    /// Marks a record to go away.
161    fn delete(&mut self, entity: &str, key: &Value) -> Result<(), SpaceError>;
162    /// What waits for a commit.
163    fn changes(&self) -> Vec<Change>;
164    /// Whether anything waits for a commit.
165    fn is_modified(&self) -> bool {
166        !self.changes().is_empty()
167    }
168    /// Keeps every change, or none of them.
169    fn commit(&mut self) -> Result<(), SpaceError>;
170    /// Throws away every change.
171    fn rollback(&mut self);
172    /// Throws away every change and reads again from storage.
173    fn refresh(&mut self) {
174        self.rollback();
175    }
176    /// Calls this when something happens to the space.
177    fn on_event(&mut self, watcher: SpaceWatcher);
178}
179
180impl ObjectSpace for Box<dyn ObjectSpace> {
181    fn entities(&self) -> Vec<EntityInfo> {
182        (**self).entities()
183    }
184    fn query(&self, entity: &str, query: &Query) -> Result<Vec<Object>, SpaceError> {
185        (**self).query(entity, query)
186    }
187    fn find(&self, entity: &str, key: &Value) -> Result<Option<Object>, SpaceError> {
188        (**self).find(entity, key)
189    }
190    fn new_object(&mut self, entity: &str) -> Result<Object, SpaceError> {
191        (**self).new_object(entity)
192    }
193    fn save(&mut self, object: Object) -> Result<(), SpaceError> {
194        (**self).save(object)
195    }
196    fn insert(&mut self, object: Object) -> Result<(), SpaceError> {
197        (**self).insert(object)
198    }
199    fn delete(&mut self, entity: &str, key: &Value) -> Result<(), SpaceError> {
200        (**self).delete(entity, key)
201    }
202    fn changes(&self) -> Vec<Change> {
203        (**self).changes()
204    }
205    fn is_modified(&self) -> bool {
206        (**self).is_modified()
207    }
208    fn commit(&mut self) -> Result<(), SpaceError> {
209        (**self).commit()
210    }
211    fn rollback(&mut self) {
212        (**self).rollback();
213    }
214    fn refresh(&mut self) {
215        (**self).refresh();
216    }
217    fn on_event(&mut self, watcher: SpaceWatcher) {
218        (**self).on_event(watcher);
219    }
220}
221
222/// A key as text, the same for equal keys.
223pub fn key_text(key: &Value) -> Option<String> {
224    match key {
225        Value::Null => None,
226        Value::Text(text) if text.is_empty() => None,
227        Value::Text(text) => Some(text.clone()),
228        Value::Number(number) if number.fract() == 0.0 => Some(format!("{}", *number as i64)),
229        Value::Number(number) => Some(number.to_string()),
230        Value::Bool(on) => Some(on.to_string()),
231        Value::Date(date) => Some(format!("{date:?}")),
232        _ => None,
233    }
234}
235
236fn compare(a: &Value, b: &Value) -> Ordering {
237    match (a, b) {
238        (Value::Null, Value::Null) => Ordering::Equal,
239        (Value::Null, _) => Ordering::Less,
240        (_, Value::Null) => Ordering::Greater,
241        (Value::Number(x), Value::Number(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
242        (Value::Text(x), Value::Text(y)) => x.cmp(y),
243        (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
244        (Value::Date(x), Value::Date(y)) => (x.days, x.seconds).cmp(&(y.days, y.seconds)),
245        _ => Ordering::Equal,
246    }
247}
248
249/// The key of a record, as text.
250pub fn key_of(entity: &EntityInfo, object: &Object) -> Option<String> {
251    entity.key_field().and_then(|field| key_text(&object.get(&field.name)))
252}
253
254/// What a unit of work has waiting, for whoever implements one: new, changed and deleted
255/// records, one change per record.
256#[derive(Clone, Debug, Default)]
257#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
258pub struct ChangeSet {
259    changes: Vec<Change>,
260}
261
262#[cfg_attr(feature = "live", ravenrs_live::expose)]
263impl ChangeSet {
264    /// What waits, in the order it came.
265    pub fn list(&self) -> &[Change] {
266        &self.changes
267    }
268
269    /// Throws it all away.
270    pub fn clear(&mut self) {
271        self.changes.clear();
272    }
273
274    /// Whether anything waits for this entity.
275    pub fn touches(&self, entity: &str) -> bool {
276        self.changes.iter().any(|change| match change {
277            Change::New(object) | Change::Updated(object) => object.entity() == entity,
278            Change::Deleted { entity: name, .. } => name == entity,
279        })
280    }
281
282    fn position(&self, entity: &EntityInfo, key: &str) -> Option<usize> {
283        self.changes.iter().position(|change| match change {
284            Change::New(object) | Change::Updated(object) => {
285                object.entity() == entity.name && key_of(entity, object).as_deref() == Some(key)
286            }
287            Change::Deleted { entity: name, key: deleted } => {
288                *name == entity.name && key_text(deleted).as_deref() == Some(key)
289            }
290        })
291    }
292
293    /// Marks a record as new or changed. `exists` says whether storage has its key.
294    pub fn save(&mut self, entity: &EntityInfo, mut object: Object, exists: bool) -> Result<(), SpaceError> {
295        entity.strip_calculated(&mut object);
296        let key = key_of(entity, &object).ok_or_else(|| SpaceError::MissingKey(entity.name.clone()))?;
297        match self.position(entity, &key) {
298            Some(at) if matches!(self.changes[at], Change::New(_)) => self.changes[at] = Change::New(object),
299            Some(at) => self.changes[at] = Change::Updated(object),
300            None if exists => self.changes.push(Change::Updated(object)),
301            None => self.changes.push(Change::New(object)),
302        }
303        Ok(())
304    }
305
306    /// Marks a record as new, refusing a key in use. `exists` says whether storage has it.
307    pub fn insert(
308        &mut self,
309        entity: &EntityInfo,
310        mut object: Object,
311        exists: bool,
312    ) -> Result<(), SpaceError> {
313        entity.strip_calculated(&mut object);
314        let key = key_of(entity, &object).ok_or_else(|| SpaceError::MissingKey(entity.name.clone()))?;
315        let waiting = self.position(entity, &key);
316        let taken = match waiting {
317            Some(at) => !matches!(self.changes[at], Change::Deleted { .. }),
318            None => exists,
319        };
320        if taken {
321            return Err(SpaceError::DuplicateKey { entity: entity.name.clone(), key });
322        }
323        match waiting {
324            // What was going away comes back as a change to it.
325            Some(at) => self.changes[at] = Change::Updated(object),
326            None => self.changes.push(Change::New(object)),
327        }
328        Ok(())
329    }
330
331    /// Marks a record to go away. `exists` says whether storage has it.
332    pub fn delete(&mut self, entity: &EntityInfo, key: &Value, exists: bool) {
333        let Some(text) = key_text(key) else { return };
334        match self.position(entity, &text) {
335            Some(at) if matches!(self.changes[at], Change::New(_)) => {
336                self.changes.remove(at);
337            }
338            Some(at) => {
339                self.changes[at] = Change::Deleted { entity: entity.name.clone(), key: key.clone() };
340            }
341            None if exists => {
342                self.changes.push(Change::Deleted { entity: entity.name.clone(), key: key.clone() });
343            }
344            None => {}
345        }
346    }
347
348    /// Puts what waits on top of what storage has, by key.
349    pub fn overlay(&self, entity: &EntityInfo, rows: &mut BTreeMap<String, Object>) {
350        for change in &self.changes {
351            match change {
352                Change::New(object) | Change::Updated(object) if object.entity() == entity.name => {
353                    if let Some(key) = key_of(entity, object) {
354                        rows.insert(key, object.clone());
355                    }
356                }
357                Change::Deleted { entity: name, key } if *name == entity.name => {
358                    if let Some(key) = key_text(key) {
359                        rows.remove(&key);
360                    }
361                }
362                _ => {}
363            }
364        }
365    }
366}
367
368/// Says whether a query fits an entity.
369pub fn check_query(entity: &EntityInfo, query: &Query) -> Result<(), SpaceError> {
370    if let Some(filter) = &query.filter {
371        let environment = crate::environment();
372        let problems = ravenrs_criteria::validate(filter, &entity.schema());
373        // A function the application added is known only where it runs.
374        let found = problems.iter().find(|problem| {
375            !matches!(problem, ravenrs_criteria::Problem::UnknownFunction(name) if environment.has_function(name))
376        });
377        if let Some(problem) = found {
378            return Err(SpaceError::BadQuery(format!("{}: {problem}", entity.name)));
379        }
380    }
381    for (field, _) in &query.sort {
382        if entity.field_named(field).is_none() {
383            return Err(SpaceError::BadQuery(format!("{} has no field {field}", entity.name)));
384        }
385    }
386    Ok(())
387}
388
389/// Runs a query over records in memory: computes, filters, sorts and takes the page.
390pub fn run_query(
391    entity: &EntityInfo,
392    objects: Vec<Object>,
393    query: &Query,
394    environment: &Environment,
395) -> Result<Vec<Object>, SpaceError> {
396    check_query(entity, query)?;
397    let objects = objects
398        .into_iter()
399        .map(|mut object| {
400            entity.compute(&mut object, environment);
401            object
402        })
403        .collect();
404    select(entity, objects, query, environment)
405}
406
407/// Filters, sorts and takes the page of records as they are, computed or not.
408pub(crate) fn select(
409    entity: &EntityInfo,
410    objects: Vec<Object>,
411    query: &Query,
412    environment: &Environment,
413) -> Result<Vec<Object>, SpaceError> {
414    let key_field = entity.key_field().map(|f| f.name.clone()).unwrap_or_default();
415    let mut found: Vec<Object> = objects
416        .into_iter()
417        .filter(|object| {
418            query.filter.as_ref().is_none_or(|filter| Scope::new(environment).row(object).matches(filter))
419        })
420        .collect();
421    found.sort_by(|a, b| {
422        query
423            .sort
424            .iter()
425            .map(|(field, ascending)| {
426                let order = compare(&a.get(field), &b.get(field));
427                if *ascending { order } else { order.reverse() }
428            })
429            .find(|order| order.is_ne())
430            .unwrap_or_else(|| compare(&a.get(&key_field), &b.get(&key_field)))
431    });
432    let page = found.into_iter().skip(query.offset);
433    let mut chosen: Vec<Object> = match query.limit {
434        Some(limit) => page.take(limit).collect(),
435        None => page.collect(),
436    };
437    if let Some(only) = &query.only {
438        let mut names: Vec<&str> = only.iter().map(String::as_str).collect();
439        names.push(&key_field);
440        for object in &mut chosen {
441            object.keep_only(&names);
442        }
443    }
444    Ok(chosen)
445}
446
447/// What comes of putting together what a person changed and what someone else kept meanwhile.
448#[derive(Clone, Debug, PartialEq)]
449#[non_exhaustive]
450#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
451pub struct Merged {
452    /// The record with both sets of changes. On a field both changed, it has this person's.
453    pub record: Object,
454    /// The fields both changed, each to something else.
455    pub conflicts: Vec<String>,
456}
457
458/// Puts together what a person changed, from `original` to `mine`, and what is `stored` now.
459/// Changes to different fields both stand, so only changing the same field takes a decision.
460pub fn merge_changes(entity: &EntityInfo, original: &Object, mine: &Object, stored: &Object) -> Merged {
461    let mut record = stored.clone();
462    let mut conflicts = Vec::new();
463    let fields = entity.fields.iter().filter(|field| {
464        !field.is_key && field.calculated.is_none() && !matches!(field.kind, FieldKind::Collection(_))
465    });
466    for field in fields {
467        let (was, changed, now) = (original.get(&field.name), mine.get(&field.name), stored.get(&field.name));
468        if changed == was {
469            continue;
470        }
471        if now != was && now != changed {
472            conflicts.push(field.name.clone());
473        }
474        record.set(&field.name, changed);
475    }
476    Merged { record, conflicts }
477}
478
479#[derive(Debug, Default)]
480struct Tables {
481    rows: BTreeMap<String, BTreeMap<String, Object>>,
482    next_key: i64,
483}
484
485/// Where a [`MemoryDatabase`] keeps its records, and what went wrong the last time it tried.
486struct Kept {
487    storage: Rc<dyn Storage>,
488    name: String,
489    problem: Option<String>,
490}
491
492/// Records kept in memory, on desktop and in the browser. Any number of spaces share them, and
493/// each sees what the others committed.
494///
495/// With [`Self::stored_in`] they are also kept in a [`Storage`] between runs: read when it
496/// opens, written after every commit. [`Self::export_json`] and [`Self::import_json`] move all
497/// of them in and out at once.
498#[derive(Clone)]
499#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
500pub struct MemoryDatabase {
501    entities: Rc<Vec<EntityInfo>>,
502    tables: Rc<RefCell<Tables>>,
503    kept: Rc<RefCell<Option<Kept>>>,
504    restored: bool,
505}
506
507impl fmt::Debug for MemoryDatabase {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        f.debug_struct("MemoryDatabase")
510            .field("entities", &self.entities.len())
511            .field("kept", &self.kept.borrow().as_ref().map(|k| k.name.clone()))
512            .finish_non_exhaustive()
513    }
514}
515
516/// The version of [`MemoryDatabase::export_json`]'s format.
517const RECORDS_FORMAT: u64 = 1;
518
519fn value_to_json(value: &Value) -> serde_json::Value {
520    match value {
521        Value::Null => serde_json::Value::Null,
522        Value::Bool(b) => serde_json::Value::Bool(*b),
523        Value::Number(n) => {
524            serde_json::Number::from_f64(*n).map_or(serde_json::Value::Null, serde_json::Value::Number)
525        }
526        Value::Text(text) => serde_json::Value::String(text.clone()),
527        Value::Date(moment) => serde_json::json!({ "days": moment.days, "seconds": moment.seconds }),
528        // A kind of value newer than this format is left out rather than misread later.
529        _ => serde_json::Value::Null,
530    }
531}
532
533fn value_from_json(json: &serde_json::Value) -> Result<Value, String> {
534    Ok(match json {
535        serde_json::Value::Null => Value::Null,
536        serde_json::Value::Bool(b) => Value::Bool(*b),
537        serde_json::Value::Number(n) => Value::Number(n.as_f64().ok_or("a number out of range")?),
538        serde_json::Value::String(text) => Value::Text(text.clone()),
539        serde_json::Value::Object(moment) => {
540            let days = moment.get("days").and_then(serde_json::Value::as_i64).ok_or("a date without days")?;
541            let seconds = moment.get("seconds").and_then(serde_json::Value::as_u64).unwrap_or(0);
542            Value::Date(DateTime {
543                days,
544                seconds: u32::try_from(seconds).map_err(|_| "seconds out of range")?,
545            })
546        }
547        serde_json::Value::Array(_) => return Err("a list where a value goes".to_owned()),
548    })
549}
550
551#[cfg_attr(feature = "live", ravenrs_live::expose)]
552impl MemoryDatabase {
553    /// A database with these entities and no records.
554    pub fn new(entities: Vec<EntityInfo>) -> Result<Self, ModelError> {
555        for entity in &entities {
556            entity.check()?;
557        }
558        Ok(Self {
559            entities: Rc::new(entities),
560            tables: Rc::new(RefCell::new(Tables { next_key: 1, ..Tables::default() })),
561            kept: Rc::new(RefCell::new(None)),
562            restored: false,
563        })
564    }
565
566    /// Keeps the records in `storage` under `name`: what is there is read now, and every commit
567    /// writes them all back. Records of entities the database no longer has are left out.
568    pub fn stored_in(
569        mut self,
570        storage: impl Storage + 'static,
571        name: impl Into<String>,
572    ) -> Result<Self, SpaceError> {
573        let name = name.into();
574        if let Some(bytes) = storage.read(&name).map_err(SpaceError::Storage)? {
575            let text = String::from_utf8(bytes).map_err(|e| SpaceError::Storage(format!("{name}: {e}")))?;
576            self.load(&text, false).map_err(|e| SpaceError::Storage(format!("{name}: {e}")))?;
577            self.restored = true;
578        }
579        *self.kept.borrow_mut() = Some(Kept { storage: Rc::new(storage), name, problem: None });
580        Ok(self)
581    }
582
583    /// Whether [`Self::stored_in`] found records kept from before, so an app can put its first
584    /// records in only once.
585    pub fn was_restored(&self) -> bool {
586        self.restored
587    }
588
589    /// What went wrong the last time the records were written to their storage, if it did.
590    pub fn storage_problem(&self) -> Option<String> {
591        self.kept.borrow().as_ref().and_then(|kept| kept.problem.clone())
592    }
593
594    /// Every record, as JSON: `{"format": 1, "next_key": 7, "records": {"Account": [{...}]}}`.
595    /// Dates are `{"days": .., "seconds": ..}`; the rest are plain JSON values.
596    pub fn export_json(&self) -> String {
597        let tables = self.tables.borrow();
598        let records: serde_json::Map<String, serde_json::Value> = tables
599            .rows
600            .iter()
601            .map(|(entity, rows)| {
602                let rows = rows
603                    .values()
604                    .map(|object| {
605                        let fields =
606                            object.values().iter().map(|(k, v)| (k.clone(), value_to_json(v))).collect();
607                        serde_json::Value::Object(fields)
608                    })
609                    .collect();
610                (entity.clone(), serde_json::Value::Array(rows))
611            })
612            .collect();
613        let whole = serde_json::json!({
614            "format": RECORDS_FORMAT,
615            "next_key": tables.next_key,
616            "records": records,
617        });
618        serde_json::to_string_pretty(&whole).unwrap_or_else(|_| "{}".to_owned())
619    }
620
621    /// Puts these records, as [`Self::export_json`] writes them, in place of all there are, and
622    /// keeps them if the database is stored. Returns how many there are. Nothing changes if
623    /// anything is wrong: an entity it does not have, a record without its key, a value that
624    /// cannot be read.
625    pub fn import_json(&self, text: &str) -> Result<usize, SpaceError> {
626        let count = self.load(text, true).map_err(SpaceError::Storage)?;
627        self.persist();
628        Ok(count)
629    }
630
631    /// Reads records into the tables, all or nothing. Strict refuses entities it does not have.
632    fn load(&self, text: &str, strict: bool) -> Result<usize, String> {
633        let whole: serde_json::Value = serde_json::from_str(text).map_err(|e| e.to_string())?;
634        let format = whole.get("format").and_then(serde_json::Value::as_u64).unwrap_or(RECORDS_FORMAT);
635        if format > RECORDS_FORMAT {
636            return Err(format!("records written by a newer version (format {format})"));
637        }
638        let mut rows: BTreeMap<String, BTreeMap<String, Object>> = BTreeMap::new();
639        let mut next_key = whole.get("next_key").and_then(serde_json::Value::as_i64).unwrap_or(1).max(1);
640        let empty = serde_json::Map::new();
641        let records = whole.get("records").and_then(serde_json::Value::as_object).unwrap_or(&empty);
642        for (name, list) in records {
643            let Some(entity) = self.entities.iter().find(|e| &e.name == name) else {
644                if strict {
645                    return Err(format!("there is no entity named {name}"));
646                }
647                continue;
648            };
649            let list = list.as_array().ok_or_else(|| format!("{name}: a list of records goes here"))?;
650            let table = rows.entry(name.clone()).or_default();
651            for record in list {
652                let fields = record.as_object().ok_or_else(|| format!("{name}: a record is an object"))?;
653                let mut values = BTreeMap::new();
654                for (field, value) in fields {
655                    values.insert(
656                        field.clone(),
657                        value_from_json(value).map_err(|e| format!("{name}.{field}: {e}"))?,
658                    );
659                }
660                let object = Object::from_values(name.clone(), values);
661                let key =
662                    key_of(entity, &object).ok_or_else(|| format!("{name}: a record without its key"))?;
663                if let Some(field) = entity.key_field()
664                    && matches!(field.kind, FieldKind::Integer)
665                    && let Value::Number(n) = object.get(&field.name)
666                {
667                    next_key = next_key.max(n as i64 + 1);
668                }
669                table.insert(key, object);
670            }
671        }
672        let count = rows.values().map(BTreeMap::len).sum();
673        let mut tables = self.tables.borrow_mut();
674        tables.rows = rows;
675        tables.next_key = next_key;
676        Ok(count)
677    }
678
679    /// Writes every record to the storage, if there is one, and remembers what went wrong.
680    fn persist(&self) {
681        let text = if self.kept.borrow().is_some() { self.export_json() } else { return };
682        if let Some(kept) = self.kept.borrow_mut().as_mut() {
683            kept.problem = kept.storage.write(&kept.name, text.as_bytes()).err();
684        }
685    }
686
687    /// A new unit of work over these records.
688    pub fn space(&self) -> MemoryObjectSpace {
689        MemoryObjectSpace {
690            database: self.clone(),
691            environment: crate::environment(),
692            changes: ChangeSet::default(),
693            watchers: Vec::new(),
694        }
695    }
696}
697
698/// A unit of work over a [`MemoryDatabase`].
699#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
700pub struct MemoryObjectSpace {
701    database: MemoryDatabase,
702    environment: Environment,
703    changes: ChangeSet,
704    watchers: Vec<SpaceWatcher>,
705}
706
707impl fmt::Debug for MemoryObjectSpace {
708    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709        f.debug_struct("MemoryObjectSpace").field("changes", &self.changes).finish_non_exhaustive()
710    }
711}
712
713#[cfg_attr(feature = "live", ravenrs_live::expose)]
714impl MemoryObjectSpace {
715    fn entity(&self, name: &str) -> Result<&EntityInfo, SpaceError> {
716        self.database
717            .entities
718            .iter()
719            .find(|e| e.name == name)
720            .ok_or_else(|| SpaceError::UnknownEntity(name.to_owned()))
721    }
722
723    fn committed_has(&self, entity: &str, key: &str) -> bool {
724        self.database.tables.borrow().rows.get(entity).is_some_and(|table| table.contains_key(key))
725    }
726
727    /// Stored records with this space's changes on top, by key.
728    fn current(&self, entity: &EntityInfo) -> BTreeMap<String, Object> {
729        let mut rows = self.database.tables.borrow().rows.get(&entity.name).cloned().unwrap_or_default();
730        self.changes.overlay(entity, &mut rows);
731        rows
732    }
733
734    fn tell(&mut self, event: SpaceEvent<'_>) -> Result<(), SpaceError> {
735        for watcher in &mut self.watchers {
736            watcher(event).map_err(SpaceError::Refused)?;
737        }
738        Ok(())
739    }
740
741    /// Says what would stop these changes, before any is applied.
742    fn check_changes(&self) -> Result<(), SpaceError> {
743        for change in self.changes.list() {
744            match change {
745                Change::New(object) => {
746                    let entity = self.entity(object.entity())?;
747                    let key =
748                        key_of(entity, object).ok_or_else(|| SpaceError::MissingKey(entity.name.clone()))?;
749                    if self.committed_has(&entity.name, &key) {
750                        return Err(SpaceError::DuplicateKey { entity: entity.name.clone(), key });
751                    }
752                    crate::check_transitions(entity, None, object)?;
753                }
754                Change::Updated(object) => {
755                    let entity = self.entity(object.entity())?;
756                    let key =
757                        key_of(entity, object).ok_or_else(|| SpaceError::MissingKey(entity.name.clone()))?;
758                    if !self.committed_has(&entity.name, &key) {
759                        return Err(SpaceError::Conflict(format!(
760                            "the record {key} of {} is gone",
761                            entity.name
762                        )));
763                    }
764                    let tables = self.database.tables.borrow();
765                    let stored = tables.rows.get(&entity.name).and_then(|rows| rows.get(&key));
766                    crate::check_transitions(entity, stored, object)?;
767                }
768                Change::Deleted { .. } => {}
769            }
770        }
771        Ok(())
772    }
773}
774
775#[cfg_attr(feature = "live", ravenrs_live::expose(part = "objectspace"))]
776impl ObjectSpace for MemoryObjectSpace {
777    fn entities(&self) -> Vec<EntityInfo> {
778        self.database.entities.as_ref().clone()
779    }
780
781    fn query(&self, entity: &str, query: &Query) -> Result<Vec<Object>, SpaceError> {
782        let info = self.entity(entity)?;
783        let objects = self.current(info).into_values().collect();
784        run_query(info, objects, query, &self.environment)
785    }
786
787    fn find(&self, entity: &str, key: &Value) -> Result<Option<Object>, SpaceError> {
788        let info = self.entity(entity)?;
789        let Some(key) = key_text(key) else { return Ok(None) };
790        Ok(self.current(info).remove(&key).map(|mut object| {
791            info.compute(&mut object, &self.environment);
792            object
793        }))
794    }
795
796    fn new_object(&mut self, entity: &str) -> Result<Object, SpaceError> {
797        let info = self.entity(entity)?;
798        let mut object = info.new_object();
799        // Numbered keys are given here, and never again, even if this record is not kept.
800        if let Some(field) = info.key_field()
801            && matches!(field.kind, FieldKind::Integer)
802            && matches!(object.get(&field.name), Value::Null)
803        {
804            let mut tables = self.database.tables.borrow_mut();
805            object.set(&field.name, Value::Number(tables.next_key as f64));
806            tables.next_key += 1;
807        }
808        Ok(object)
809    }
810
811    fn save(&mut self, object: Object) -> Result<(), SpaceError> {
812        let info = self.entity(object.entity())?.clone();
813        let exists = key_of(&info, &object).is_some_and(|key| self.committed_has(&info.name, &key));
814        self.changes.save(&info, object, exists)
815    }
816
817    fn insert(&mut self, object: Object) -> Result<(), SpaceError> {
818        let info = self.entity(object.entity())?.clone();
819        let exists = key_of(&info, &object).is_some_and(|key| self.committed_has(&info.name, &key));
820        self.changes.insert(&info, object, exists)
821    }
822
823    fn delete(&mut self, entity: &str, key: &Value) -> Result<(), SpaceError> {
824        let info = self.entity(entity)?.clone();
825        let exists = key_text(key).is_some_and(|text| self.committed_has(&info.name, &text));
826        self.changes.delete(&info, key, exists);
827        Ok(())
828    }
829
830    fn changes(&self) -> Vec<Change> {
831        self.changes.list().to_vec()
832    }
833
834    fn commit(&mut self) -> Result<(), SpaceError> {
835        self.check_changes()?;
836        let changes = self.changes.list().to_vec();
837        self.tell(SpaceEvent::Committing(&changes))?;
838        {
839            let mut tables = self.database.tables.borrow_mut();
840            for change in &changes {
841                match change {
842                    Change::New(object) | Change::Updated(object) => {
843                        let info = self.database.entities.iter().find(|e| e.name == object.entity());
844                        if let Some(key) = info.and_then(|info| key_of(info, object)) {
845                            tables
846                                .rows
847                                .entry(object.entity().to_owned())
848                                .or_default()
849                                .insert(key, object.clone());
850                        }
851                    }
852                    Change::Deleted { entity, key } => {
853                        if let (Some(table), Some(key)) = (tables.rows.get_mut(entity), key_text(key)) {
854                            table.remove(&key);
855                        }
856                    }
857                }
858            }
859        }
860        self.changes.clear();
861        self.database.persist();
862        // The commit stands; someone watching after it cannot undo it.
863        let _ = self.tell(SpaceEvent::Committed(&changes));
864        Ok(())
865    }
866
867    fn rollback(&mut self) {
868        self.changes.clear();
869        let _ = self.tell(SpaceEvent::RolledBack);
870    }
871
872    fn on_event(&mut self, watcher: SpaceWatcher) {
873        self.watchers.push(watcher);
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880    use crate::FieldInfo;
881
882    fn database() -> MemoryDatabase {
883        MemoryDatabase::new(vec![
884            EntityInfo::new("Account")
885                .field(FieldInfo::new("id", FieldKind::Integer).key())
886                .field(FieldInfo::new("name", FieldKind::Text).default_value("Nova"))
887                .field(FieldInfo::new("cents", FieldKind::Money).default_value(0.0))
888                .field(FieldInfo::new("double", FieldKind::Money).calculated("[cents] * 2")),
889            EntityInfo::new("Bank")
890                .field(FieldInfo::new("code", FieldKind::Text).typed_key())
891                .field(FieldInfo::new("name", FieldKind::Text)),
892        ])
893        .expect("valid")
894    }
895
896    fn add(space: &mut MemoryObjectSpace, name: &str, cents: f64) -> Value {
897        let mut object = space.new_object("Account").expect("the entity");
898        object.set("name", name);
899        object.set("cents", cents);
900        let key = object.get("id");
901        space.save(object).expect("saves");
902        key
903    }
904
905    fn names(space: &MemoryObjectSpace, query: &Query) -> Vec<String> {
906        let found = space.query("Account", query).expect("reads");
907        found.iter().map(|o| if let Value::Text(t) = o.get("name") { t } else { String::new() }).collect()
908    }
909
910    #[test]
911    fn nothing_is_kept_until_the_commit_and_the_space_sees_its_own_changes() {
912        let db = database();
913        let (mut mine, other) = (db.space(), db.space());
914        add(&mut mine, "A", 1.0);
915        assert!(mine.is_modified());
916        assert_eq!(names(&mine, &Query::all()), ["A"]);
917        assert!(names(&other, &Query::all()).is_empty());
918        mine.commit().expect("commits");
919        assert!(!mine.is_modified());
920        assert_eq!(names(&other, &Query::all()), ["A"]);
921    }
922
923    #[test]
924    fn a_rollback_throws_away_new_changed_and_deleted_records() {
925        let db = database();
926        let mut space = db.space();
927        let (a, b) = (add(&mut space, "A", 1.0), add(&mut space, "B", 2.0));
928        space.commit().expect("commits");
929        let mut changed = space.find("Account", &a).expect("reads").expect("there");
930        changed.set("name", "A2");
931        space.save(changed).expect("saves");
932        space.delete("Account", &b).expect("deletes");
933        add(&mut space, "C", 3.0);
934        assert_eq!(names(&space, &Query::all()), ["A2", "C"]);
935        assert_eq!(space.changes().len(), 3);
936        space.rollback();
937        assert_eq!(names(&space, &Query::all()), ["A", "B"]);
938    }
939
940    #[test]
941    fn a_new_record_deleted_before_the_commit_was_never_there() {
942        let db = database();
943        let mut space = db.space();
944        let key = add(&mut space, "A", 1.0);
945        space.delete("Account", &key).expect("deletes");
946        assert!(!space.is_modified());
947        let again = add(&mut space, "B", 1.0);
948        let mut object = space.find("Account", &again).expect("reads").expect("there");
949        object.set("name", "B2");
950        space.save(object).expect("saves");
951        assert!(matches!(space.changes().as_slice(), [Change::New(_)]), "still new after a change");
952    }
953
954    #[test]
955    fn numbered_keys_are_not_given_twice() {
956        let db = database();
957        let (mut a, mut b) = (db.space(), db.space());
958        let (one, two) = (add(&mut a, "A", 1.0), add(&mut b, "B", 1.0));
959        assert_ne!(one, two);
960        a.commit().expect("commits");
961        b.commit().expect("commits");
962        assert_eq!(names(&a, &Query::all()).len(), 2);
963    }
964
965    #[test]
966    fn typed_keys_are_needed_and_cannot_repeat() {
967        let db = database();
968        let mut space = db.space();
969        let empty = space.new_object("Bank").expect("the entity");
970        assert_eq!(space.save(empty), Err(SpaceError::MissingKey("Bank".into())));
971        let mut object = space.new_object("Bank").expect("the entity");
972        object.set("code", "001");
973        space.save(object.clone()).expect("saves");
974        let mut other = db.space();
975        other.save(object).expect("saves");
976        space.commit().expect("commits");
977        assert_eq!(
978            other.commit(),
979            Err(SpaceError::DuplicateKey { entity: "Bank".into(), key: "001".into() })
980        );
981        assert!(other.is_modified(), "the changes wait for a decision");
982    }
983
984    #[test]
985    fn inserting_does_not_take_over_a_record() {
986        let db = database();
987        let mut space = db.space();
988        let bank = |name: &str| {
989            let mut object = db.space().new_object("Bank").expect("the entity");
990            object.set("code", "001");
991            object.set("name", name);
992            object
993        };
994        space.insert(bank("Um")).expect("a new key");
995        let waiting = SpaceError::DuplicateKey { entity: "Bank".into(), key: "001".into() };
996        assert_eq!(space.insert(bank("Dois")), Err(waiting.clone()), "waiting for the commit");
997        space.commit().expect("commits");
998        assert_eq!(space.insert(bank("Dois")), Err(waiting), "already kept");
999        let key = Value::Text("001".into());
1000        assert_eq!(
1001            space.find("Bank", &key).expect("reads").expect("there").get("name"),
1002            Value::Text("Um".into())
1003        );
1004        space.delete("Bank", &key).expect("deletes");
1005        space.insert(bank("Tres")).expect("free again");
1006        space.commit().expect("commits");
1007        assert_eq!(
1008            space.find("Bank", &key).expect("reads").expect("there").get("name"),
1009            Value::Text("Tres".into())
1010        );
1011    }
1012
1013    #[test]
1014    fn a_commit_keeps_all_or_nothing() {
1015        let db = database();
1016        let mut first = db.space();
1017        let key = add(&mut first, "A", 1.0);
1018        first.commit().expect("commits");
1019        let mut stale = db.space();
1020        let mut object = stale.find("Account", &key).expect("reads").expect("there");
1021        object.set("name", "stale");
1022        stale.save(object).expect("saves");
1023        add(&mut stale, "Other", 1.0);
1024        first.delete("Account", &key).expect("deletes");
1025        first.commit().expect("commits");
1026        assert!(matches!(stale.commit(), Err(SpaceError::Conflict(_))));
1027        assert!(names(&first, &Query::all()).is_empty(), "the new record did not get in either");
1028    }
1029
1030    #[test]
1031    fn filters_sort_and_pages_work_over_calculated_fields() {
1032        let db = database();
1033        let mut space = db.space();
1034        for (name, cents) in [("C", 30.0), ("A", 10.0), ("B", 20.0), ("D", 10.0)] {
1035            add(&mut space, name, cents);
1036        }
1037        space.commit().expect("commits");
1038        let filter = Criteria::parse("[double] >= 40").expect("parses");
1039        let sorted = Query::all().filter(filter).sort("cents", false);
1040        assert_eq!(names(&space, &sorted), ["C", "B"]);
1041        let by_two = Query::all().sort("cents", true).sort("name", false);
1042        assert_eq!(names(&space, &by_two), ["D", "A", "B", "C"]);
1043        assert_eq!(names(&space, &by_two.clone().page(1, 2)), ["A", "B"]);
1044        let found = space.query("Account", &Query::all().page(0, 1)).expect("reads");
1045        assert_eq!(found[0].get("double"), Value::Number(60.0), "in key order, computed");
1046    }
1047
1048    #[test]
1049    fn calculated_values_are_not_stored() {
1050        let db = database();
1051        let mut space = db.space();
1052        let key = add(&mut space, "A", 5.0);
1053        let mut object = space.find("Account", &key).expect("reads").expect("there");
1054        assert_eq!(object.get("double"), Value::Number(10.0));
1055        object.set("cents", 7.0);
1056        space.save(object).expect("saves");
1057        let found = space.find("Account", &key).expect("reads").expect("there");
1058        assert_eq!(found.get("double"), Value::Number(14.0));
1059        let changes = space.changes();
1060        let [Change::New(pending)] = changes.as_slice() else {
1061            panic!("one new record");
1062        };
1063        assert!(!pending.values().contains_key("double"));
1064    }
1065
1066    #[test]
1067    fn a_new_record_starts_with_its_defaults() {
1068        let db = database();
1069        let mut space = db.space();
1070        let object = space.new_object("Account").expect("the entity");
1071        assert_eq!(object.get("name"), Value::Text("Nova".into()));
1072        assert!(!space.is_modified(), "not until it is saved");
1073    }
1074
1075    #[test]
1076    fn a_watcher_can_refuse_a_commit_and_hears_the_rest() {
1077        let db = database();
1078        let mut space = db.space();
1079        let heard = Rc::new(RefCell::new(Vec::new()));
1080        let log = heard.clone();
1081        let refuse = Rc::new(std::cell::Cell::new(true));
1082        let gate = refuse.clone();
1083        space.on_event(Box::new(move |event| match event {
1084            SpaceEvent::Committing(_) if gate.get() => Err("not today".to_owned()),
1085            SpaceEvent::Committing(changes) => {
1086                log.borrow_mut().push(format!("committing {}", changes.len()));
1087                Ok(())
1088            }
1089            SpaceEvent::Committed(_) => {
1090                log.borrow_mut().push("committed".to_owned());
1091                Ok(())
1092            }
1093            _ => {
1094                log.borrow_mut().push("rolled back".to_owned());
1095                Ok(())
1096            }
1097        }));
1098        add(&mut space, "A", 1.0);
1099        assert_eq!(space.commit(), Err(SpaceError::Refused("not today".into())));
1100        assert!(space.is_modified(), "refused, so it still waits");
1101        refuse.set(false);
1102        space.commit().expect("commits");
1103        space.rollback();
1104        assert_eq!(*heard.borrow(), ["committing 1", "committed", "rolled back"]);
1105    }
1106
1107    #[test]
1108    fn what_does_not_fit_the_entity_says_so() {
1109        let db = database();
1110        let mut space = db.space();
1111        let bad = Query::all().filter(Criteria::parse("[nope] = 1").expect("parses"));
1112        assert!(matches!(space.query("Account", &bad), Err(SpaceError::BadQuery(_))));
1113        let bad = Query::all().sort("nope", true);
1114        assert!(matches!(space.query("Account", &bad), Err(SpaceError::BadQuery(_))));
1115        assert_eq!(space.new_object("Nope").map(|_| ()), Err(SpaceError::UnknownEntity("Nope".into())));
1116    }
1117}