1use 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#[derive(Clone, Debug, PartialEq, Eq)]
16#[non_exhaustive]
17#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
18pub enum SpaceError {
19 UnknownEntity(String),
21 BadQuery(String),
23 MissingKey(String),
25 DuplicateKey {
27 entity: String,
29 key: String,
31 },
32 Conflict(String),
34 Refused(String),
36 Denied(String),
38 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#[derive(Clone, Debug, Default)]
63#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
64pub struct Query {
65 pub filter: Option<Criteria>,
67 pub sort: Vec<(String, bool)>,
69 pub offset: usize,
71 pub limit: Option<usize>,
73 pub only: Option<Vec<String>>,
75}
76
77#[cfg_attr(feature = "live", ravenrs_live::expose)]
78impl Query {
79 pub fn all() -> Self {
81 Self::default()
82 }
83
84 pub fn filter(mut self, filter: Criteria) -> Self {
86 self.filter = Some(filter);
87 self
88 }
89
90 pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
92 self.sort.push((field.into(), ascending));
93 self
94 }
95
96 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 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#[derive(Clone, Debug, PartialEq)]
112#[non_exhaustive]
113#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
114pub enum Change {
115 New(Object),
117 Updated(Object),
119 Deleted {
121 entity: String,
123 key: Value,
125 },
126}
127
128#[derive(Clone, Copy, Debug)]
130#[non_exhaustive]
131pub enum SpaceEvent<'a> {
132 Committing(&'a [Change]),
134 Committed(&'a [Change]),
136 RolledBack,
138}
139
140pub type SpaceWatcher = Box<dyn FnMut(SpaceEvent<'_>) -> Result<(), String>>;
142
143pub trait ObjectSpace {
147 fn entities(&self) -> Vec<EntityInfo>;
149 fn query(&self, entity: &str, query: &Query) -> Result<Vec<Object>, SpaceError>;
151 fn find(&self, entity: &str, key: &Value) -> Result<Option<Object>, SpaceError>;
153 fn new_object(&mut self, entity: &str) -> Result<Object, SpaceError>;
155 fn save(&mut self, object: Object) -> Result<(), SpaceError>;
157 fn insert(&mut self, object: Object) -> Result<(), SpaceError>;
160 fn delete(&mut self, entity: &str, key: &Value) -> Result<(), SpaceError>;
162 fn changes(&self) -> Vec<Change>;
164 fn is_modified(&self) -> bool {
166 !self.changes().is_empty()
167 }
168 fn commit(&mut self) -> Result<(), SpaceError>;
170 fn rollback(&mut self);
172 fn refresh(&mut self) {
174 self.rollback();
175 }
176 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
222pub 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
249pub 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#[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 pub fn list(&self) -> &[Change] {
266 &self.changes
267 }
268
269 pub fn clear(&mut self) {
271 self.changes.clear();
272 }
273
274 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 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 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 Some(at) => self.changes[at] = Change::Updated(object),
326 None => self.changes.push(Change::New(object)),
327 }
328 Ok(())
329 }
330
331 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 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
368pub 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 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
389pub 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
407pub(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#[derive(Clone, Debug, PartialEq)]
449#[non_exhaustive]
450#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
451pub struct Merged {
452 pub record: Object,
454 pub conflicts: Vec<String>,
456}
457
458pub 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
485struct Kept {
487 storage: Rc<dyn Storage>,
488 name: String,
489 problem: Option<String>,
490}
491
492#[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
516const 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 _ => 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 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 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 pub fn was_restored(&self) -> bool {
586 self.restored
587 }
588
589 pub fn storage_problem(&self) -> Option<String> {
591 self.kept.borrow().as_ref().and_then(|kept| kept.problem.clone())
592 }
593
594 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 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 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 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 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#[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 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 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 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 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}