entity_inmemory/
lib.rs

1use entity::{
2    Database, DatabaseError, DatabaseResult, EdgeDeletionPolicy, Ent, Filter, Id, IdAllocator,
3    Predicate, Primitive, Query, Value, EPHEMERAL_ID,
4};
5use std::{
6    collections::{HashMap, HashSet},
7    sync::Mutex,
8};
9
10type EntIdSet = HashSet<Id>;
11
12/// Represents an in-memory database that performs synchronous insertion,
13/// retrieval, and removal. If the feature `serde` is enabled, this database
14/// can be serialized and deserialized.
15#[cfg_attr(feature = "serde-1", derive(serde::Serialize, serde::Deserialize))]
16pub struct InmemoryDatabase {
17    /// Primary ent storage
18    ents: Mutex<HashMap<Id, Box<dyn Ent>>>,
19
20    /// Type matching from specific ents to all ids of those ents
21    ents_of_type: Mutex<HashMap<String, EntIdSet>>,
22
23    /// Id allocator for ents
24    alloc: Mutex<IdAllocator>,
25}
26
27impl InmemoryDatabase {
28    /// Creates a new instance of an in-memory database
29    pub fn new() -> Self {
30        Self::default()
31    }
32}
33
34impl Default for InmemoryDatabase {
35    /// Creates a new, empty database entry
36    fn default() -> Self {
37        Self {
38            ents: Mutex::new(HashMap::new()),
39            ents_of_type: Mutex::new(HashMap::new()),
40            alloc: Mutex::new(IdAllocator::new()),
41        }
42    }
43}
44
45impl Database for InmemoryDatabase {
46    fn get_all(&self, ids: Vec<Id>) -> DatabaseResult<Vec<Box<dyn Ent>>> {
47        ids.into_iter()
48            .filter_map(|id| self.get(id).transpose())
49            .collect()
50    }
51
52    fn find_all(&self, query: Query) -> DatabaseResult<Vec<Box<dyn Ent>>> {
53        let mut pipeline: Option<EntIdSet> = None;
54
55        for filter in query {
56            let mut_pipeline = pipeline.get_or_insert_with(|| prefill_ids(self, &filter));
57
58            // If our filter is the special IntoEdge case, we don't want to
59            // actually filter out ids but rather transform them into the ids
60            // of their edge
61            match filter {
62                Filter::IntoEdge(name) => {
63                    pipeline = Some(
64                        mut_pipeline
65                            .iter()
66                            .flat_map(|id| {
67                                self.get(*id)
68                                    .map(|maybe_ent| {
69                                        maybe_ent
70                                            .and_then(|ent| {
71                                                ent.edge(&name).map(|edge| edge.to_ids())
72                                            })
73                                            .unwrap_or_default()
74                                    })
75                                    .unwrap_or_default()
76                            })
77                            .collect(),
78                    )
79                }
80                // Otherwise, the filter is a traditional case where we will
81                // strip out ids by the filter
82                f => {
83                    mut_pipeline.retain(|id| filter_id(self, id, &f));
84                }
85            }
86        }
87
88        pipeline
89            .unwrap_or_default()
90            .into_iter()
91            .filter_map(|id| self.get(id).transpose())
92            .collect()
93    }
94
95    fn get(&self, id: Id) -> DatabaseResult<Option<Box<dyn Ent>>> {
96        let maybe_ent = self
97            .ents
98            .lock()
99            .unwrap()
100            .get(&id)
101            .map(|ent| dyn_clone::clone_box(ent.as_ref()));
102
103        // If we found an ent without a database connection, attempt to fill
104        // it in with the global database if it exists
105        Ok(if let Some(mut ent) = maybe_ent {
106            if !ent.is_connected() {
107                ent.connect(entity::global::db());
108            }
109            Some(ent)
110        } else {
111            None
112        })
113    }
114
115    fn remove(&self, id: Id) -> DatabaseResult<bool> {
116        // Remove the ent and, if it has an associated schema, we process
117        // each of the edges identified in the schema based on deletion attributes
118        if let Some(ent) = self.ents.lock().unwrap().remove(&id) {
119            for edge in ent.edges() {
120                match edge.deletion_policy() {
121                    // If shallow deletion, we only want to remove the connections
122                    // back to this ent from the corresponding ents
123                    EdgeDeletionPolicy::ShallowDelete => {
124                        for edge_id in edge.to_ids() {
125                            if let Some(ent) = self.ents.lock().unwrap().get_mut(&edge_id) {
126                                for mut edge in ent.edges() {
127                                    let _ = edge.value_mut().remove_ids(Some(edge_id));
128                                    let name = edge.name().to_string();
129                                    let _ = ent.update_edge(&name, edge.into_value());
130                                }
131                            }
132                        }
133                    }
134                    // If deep deletion, we want to remove the ents connected
135                    // by the edge
136                    EdgeDeletionPolicy::DeepDelete => {
137                        for id in edge.to_ids() {
138                            let _ = self.remove(id);
139                        }
140                    }
141                    // If deletion policy is nothing, then do nothing
142                    EdgeDeletionPolicy::Nothing => {}
143                }
144            }
145
146            // Remove the id from our type mapping if it is there
147            self.ents_of_type
148                .lock()
149                .unwrap()
150                .entry(ent.r#type().to_string())
151                .and_modify(|e| {
152                    e.remove(&id);
153                });
154
155            // Add the id to the freed ids available in the allocator
156            self.alloc.lock().unwrap().extend(vec![id]);
157
158            Ok(true)
159        } else {
160            Ok(false)
161        }
162    }
163
164    fn insert(&self, mut ent: Box<dyn Ent>) -> DatabaseResult<Id> {
165        // Get the id of the ent, swapping out the ephemeral id
166        let id = ent.id();
167        let id = if id == EPHEMERAL_ID {
168            if let Some(id) = self.alloc.lock().unwrap().next() {
169                id
170            } else {
171                return Err(DatabaseError::EntCapacityReached);
172            }
173        } else {
174            self.alloc.lock().unwrap().mark_external_id(id);
175            id
176        };
177
178        // Update the ent's id to match what is actually to be used
179        ent.set_id(id);
180
181        // Clear any cache before saving the ent
182        ent.clear_cache();
183
184        // Update the ent's last_updated to be the current time
185        ent.mark_updated().map_err(|e| DatabaseError::Other {
186            source: Box::from(e),
187        })?;
188
189        // Add our ent's id to the set of ids associated with the ent's type
190        self.ents_of_type
191            .lock()
192            .unwrap()
193            .entry(ent.r#type().to_string())
194            .or_insert_with(HashSet::new)
195            .insert(id);
196
197        // Add our ent to the primary database
198        self.ents.lock().unwrap().insert(id, ent);
199
200        Ok(id)
201    }
202}
203
204impl InmemoryDatabase {
205    /// Returns ids of all ents stored in the database
206    pub fn ids(&self) -> EntIdSet {
207        self.ents.lock().unwrap().keys().copied().collect()
208    }
209
210    /// Returns true if database contains the provided id
211    pub fn has_id(&self, id: Id) -> bool {
212        self.ents.lock().unwrap().contains_key(&id)
213    }
214
215    /// Returns ids of all ents for the given type
216    pub fn ids_for_type(&self, r#type: &str) -> EntIdSet {
217        self.ents_of_type
218            .lock()
219            .unwrap()
220            .get(r#type)
221            .cloned()
222            .unwrap_or_default()
223    }
224}
225
226/// Called once when first beginning to filter to determine which ent ids
227/// to start with based on the leading filter
228///
229/// 1. If lead filter by id equality, will only include those ids that match
230///    the predicate
231/// 2. If lead filter by type equality, will only include those ids that equal
232///    the type (or many types if wrapped in Or)
233/// 3. Any other variation of id/type filter or other kind of filter will
234///    result in the more expensive pulling of all ids
235fn prefill_ids(db: &InmemoryDatabase, filter: &Filter) -> EntIdSet {
236    fn from_id_predicate(
237        db: &InmemoryDatabase,
238        p: &Predicate,
239        mut ids: EntIdSet,
240    ) -> Option<EntIdSet> {
241        match p {
242            Predicate::Equals(Value::Primitive(Primitive::Number(id))) => Some({
243                ids.insert(id.to_usize());
244                ids
245            }),
246            Predicate::Or(list) => list.iter().fold(Some(ids), |ids, p| match ids {
247                Some(ids) => from_id_predicate(db, p, ids),
248                None => None,
249            }),
250            _ => None,
251        }
252    }
253
254    fn from_type_predicate(
255        db: &InmemoryDatabase,
256        p: &Predicate,
257        mut ids: EntIdSet,
258    ) -> Option<EntIdSet> {
259        match p {
260            Predicate::Equals(Value::Text(t)) => Some({
261                ids.extend(db.ids_for_type(t));
262                ids
263            }),
264            Predicate::Or(list) => list.iter().fold(Some(ids), |ids, p| match ids {
265                Some(ids) => from_type_predicate(db, p, ids),
266                None => None,
267            }),
268            _ => None,
269        }
270    }
271
272    match filter {
273        // If leading with id, support Equals and Or(Equals(...), ...) for
274        // specific ids; otherwise, too hard to figure out so we pull in all ids
275        Filter::Id(p) => {
276            from_id_predicate(db, p.as_untyped(), EntIdSet::new()).unwrap_or_else(|| db.ids())
277        }
278
279        // If leading with type, support Equals and Or(Equals(...), ...) for
280        // specific ids; otherwise, too hard to figure out so we pull in all ids
281        Filter::Type(p) => {
282            from_type_predicate(db, p.as_untyped(), EntIdSet::new()).unwrap_or_else(|| db.ids())
283        }
284
285        // Otherwise, currently no cached/indexed way to look up (yet)
286        // TODO: Support database field indexing so equality of a field can
287        //       be used for faster id lookup; do the same for timestamp fields
288        _ => db.ids(),
289    }
290}
291
292fn filter_id(db: &InmemoryDatabase, id: &Id, filter: &Filter) -> bool {
293    match filter {
294        Filter::Id(p) => p.check(*id),
295        Filter::Type(p) => with_ent(db, id, |ent| p.check(ent.r#type().to_string())),
296        Filter::Created(p) => with_ent(db, id, |ent| p.check(ent.created())),
297        Filter::LastUpdated(p) => with_ent(db, id, |ent| p.check(ent.last_updated())),
298        Filter::Field(name, p) => with_ent(db, id, |ent| match ent.field(name) {
299            Some(value) => p.check(&value),
300            None => false,
301        }),
302        Filter::Edge(name, f) => with_ent(db, id, |ent| match ent.edge(name) {
303            Some(edge) => edge.to_ids().iter().any(|id| filter_id(db, id, f)),
304            None => false,
305        }),
306
307        // NOTE: Logically, this should be impossible to reach since we only
308        //       call this when we know that the filter is not a transformation
309        Filter::IntoEdge(_) => unreachable!("Bug: Transformation in filter"),
310    }
311}
312
313fn with_ent<F: Fn(Box<dyn Ent>) -> bool>(db: &InmemoryDatabase, id: &Id, f: F) -> bool {
314    db.get(*id)
315        .map(|maybe_ent| maybe_ent.map(f).unwrap_or_default())
316        .unwrap_or_default()
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use entity::{Predicate as P, TypedPredicate as TP, *};
323
324    /// Creates a new database with some test entries used throughout
325    ///
326    /// IDs: 1-3 ~ are type1 with no fields or edges
327    /// IDs: 4-6 ~ are type2 with value fields and no edges
328    /// IDs: 7-9 ~ are type3 with collection fields and no edges
329    /// IDs: 10-12 ~ are type4 with edges to 1-9 and no fields
330    fn new_test_database() -> InmemoryDatabase {
331        let db = InmemoryDatabase::default();
332
333        // 1-3 have no fields or edges
334        let _ = db
335            .insert(Box::from(UntypedEnt::from_collections(1, vec![], vec![])))
336            .unwrap();
337        let _ = db
338            .insert(Box::from(UntypedEnt::from_collections(2, vec![], vec![])))
339            .unwrap();
340        let _ = db
341            .insert(Box::from(UntypedEnt::from_collections(3, vec![], vec![])))
342            .unwrap();
343
344        // 4-6 have value fields only
345        let _ = db
346            .insert(Box::from(UntypedEnt::from_collections(
347                4,
348                vec![Field::new("a", 1), Field::new("b", 2)],
349                vec![],
350            )))
351            .unwrap();
352        let _ = db
353            .insert(Box::from(UntypedEnt::from_collections(
354                5,
355                vec![Field::new("a", 3), Field::new("b", 4)],
356                vec![],
357            )))
358            .unwrap();
359        let _ = db
360            .insert(Box::from(UntypedEnt::from_collections(
361                6,
362                vec![Field::new("a", 5), Field::new("b", 6)],
363                vec![],
364            )))
365            .unwrap();
366
367        // 7-9 have collection fields only
368        let _ = db
369            .insert(Box::from(UntypedEnt::from_collections(
370                7,
371                vec![Field::new(
372                    "f",
373                    Value::from(
374                        vec![(String::from("a"), 3), (String::from("b"), 5)]
375                            .into_iter()
376                            .collect::<HashMap<String, u8>>(),
377                    ),
378                )],
379                vec![],
380            )))
381            .unwrap();
382        let _ = db
383            .insert(Box::from(UntypedEnt::from_collections(
384                8,
385                vec![Field::new("f", vec![1, 2])],
386                vec![],
387            )))
388            .unwrap();
389        let _ = db
390            .insert(Box::from(UntypedEnt::from_collections(
391                9,
392                vec![Field::new(
393                    "f",
394                    Value::from(
395                        vec![
396                            (String::from("a"), Value::from(vec![1, 2])),
397                            (String::from("b"), Value::from(vec![3, 4])),
398                        ]
399                        .into_iter()
400                        .collect::<HashMap<String, Value>>(),
401                    ),
402                )],
403                vec![],
404            )))
405            .unwrap();
406
407        // 10-12 have edges only
408        let _ = db
409            .insert(Box::from(UntypedEnt::from_collections(
410                10,
411                vec![],
412                vec![
413                    Edge::new("a", 1),
414                    Edge::new("b", vec![3, 4, 5]),
415                    Edge::new("c", None),
416                ],
417            )))
418            .unwrap();
419        let _ = db
420            .insert(Box::from(UntypedEnt::from_collections(
421                11,
422                vec![],
423                vec![Edge::new("a", 2), Edge::new("b", vec![1, 2, 3, 4, 5, 6])],
424            )))
425            .unwrap();
426        let _ = db
427            .insert(Box::from(UntypedEnt::from_collections(
428                12,
429                vec![],
430                vec![
431                    Edge::new("a", 3),
432                    Edge::new("b", vec![]),
433                    Edge::new("c", Some(8)),
434                ],
435            )))
436            .unwrap();
437
438        db
439    }
440
441    fn query_and_assert<Q: Into<Query>>(db: &InmemoryDatabase, query: Q, expected: &[Id]) {
442        let query = query.into();
443        let results = db
444            .find_all(query.clone())
445            .expect("Failed to retrieve ents")
446            .iter()
447            .map(|ent| ent.id())
448            .collect::<HashSet<Id>>();
449        assert_eq!(
450            results,
451            expected.into_iter().copied().collect(),
452            "{:?}\nExpected: {:?}, Actual: {:?}",
453            query,
454            expected,
455            results
456        );
457    }
458
459    #[test]
460    fn insert_should_replace_ephemeral_id_with_allocator_id() {
461        let db = InmemoryDatabase::default();
462
463        let ent = UntypedEnt::empty_with_id(EPHEMERAL_ID);
464        let id = db.insert(Box::from(ent)).expect("Failed to insert ent");
465        assert_ne!(id, EPHEMERAL_ID);
466
467        let ent = db.get(id).expect("Failed to get ent").expect("Ent missing");
468        assert_eq!(ent.id(), id);
469    }
470
471    #[test]
472    fn insert_should_update_the_last_updated_time_with_the_current_time() {
473        let db = InmemoryDatabase::default();
474
475        let ent = UntypedEnt::empty_with_id(EPHEMERAL_ID);
476        let last_updated = ent.last_updated();
477        std::thread::sleep(std::time::Duration::from_millis(10));
478
479        let id = db.insert(Box::from(ent)).expect("Failed to insert ent");
480        let ent = db.get(id).expect("Failed to get ent").expect("Ent missing");
481        assert!(ent.last_updated() > last_updated);
482    }
483
484    #[test]
485    fn insert_should_add_a_new_ent_using_its_id() {
486        let db = InmemoryDatabase::default();
487
488        let ent = UntypedEnt::empty_with_id(999);
489        let id = db.insert(Box::from(ent)).expect("Failed to insert ent");
490        assert_eq!(id, 999);
491
492        let ent = db
493            .get(999)
494            .expect("Failed to get ent")
495            .expect("Ent missing");
496        assert_eq!(ent.id(), 999);
497        assert_eq!(db.alloc.lock().unwrap().next(), Some(1000));
498    }
499
500    #[test]
501    fn insert_should_overwrite_an_existing_ent_with_the_same_id() {
502        let db = InmemoryDatabase::default();
503
504        let ent = UntypedEnt::from_collections(999, vec![Field::new("field1", 3)], vec![]);
505        let _ = db.insert(Box::from(ent)).expect("Failed to insert ent");
506
507        let ent = db
508            .get(999)
509            .expect("Failed to get ent")
510            .expect("Ent missing");
511        assert_eq!(ent.field("field1").expect("Field missing"), Value::from(3));
512    }
513
514    #[test]
515    fn insert_should_reset_all_computed_field_caches_to_none() {
516        let db = InmemoryDatabase::default();
517
518        // Verify that a computed field is reset to None
519        let ent = UntypedEnt::from_collections(
520            999,
521            vec![Field::new_with_attributes(
522                "field1",
523                Some(3),
524                vec![FieldAttribute::Computed],
525            )],
526            vec![],
527        );
528        let _ = db.insert(Box::from(ent)).expect("Failed to insert ent");
529
530        let ent = db
531            .get(999)
532            .expect("Failed to get ent")
533            .expect("Ent missing");
534        assert_eq!(
535            ent.field("field1").expect("Field missing"),
536            Value::Optional(None)
537        );
538    }
539
540    #[test]
541    fn get_should_return_an_ent_by_id() {
542        use entity::DatabaseRc;
543        let db = InmemoryDatabase::default();
544
545        let result = db.get(999).expect("Failed to get ent");
546        assert!(result.is_none(), "Unexpectedly acquired ent");
547
548        let _ = db
549            .insert(Box::from(UntypedEnt::empty_with_id(999)))
550            .unwrap();
551
552        let result = db.get(999).expect("Failed to get ent");
553        assert!(result.is_some(), "Unexpectedly missing ent");
554        assert!(
555            !result.unwrap().is_connected(),
556            "Ent unexpectedly connected to database"
557        );
558
559        // Verify that if a global database is available, it will populate
560        let db = DatabaseRc::new(Box::new(db));
561        entity::global::with_db_from_rc(DatabaseRc::clone(&db), || {
562            let result = db.get(999).expect("Failed to get ent");
563            assert!(result.is_some(), "Unexpectedly missing ent");
564            assert!(
565                result.unwrap().is_connected(),
566                "Ent unexpectedly not connected to database"
567            );
568        });
569    }
570
571    #[test]
572    fn remove_should_remove_an_ent_by_id() {
573        let db = InmemoryDatabase::default();
574
575        let _ = db.remove(999).expect("Failed to remove ent");
576
577        let _ = db
578            .insert(Box::from(UntypedEnt::empty_with_id(999)))
579            .unwrap();
580        assert!(db.get(999).unwrap().is_some(), "Failed to set up ent");
581
582        let _ = db.remove(999).expect("Failed to remove ent");
583        assert!(db.get(999).unwrap().is_none(), "Did not remove ent");
584
585        // Id allocator should indicate that id has been freed
586        assert_eq!(db.alloc.lock().unwrap().freed(), &[999]);
587    }
588
589    #[test]
590    fn get_all_should_return_all_ents_with_associated_ids() {
591        let db = InmemoryDatabase::default();
592
593        let _ = db.insert(Box::from(UntypedEnt::empty_with_id(1))).unwrap();
594        let _ = db.insert(Box::from(UntypedEnt::empty_with_id(2))).unwrap();
595        let _ = db.insert(Box::from(UntypedEnt::empty_with_id(3))).unwrap();
596
597        let results = db
598            .get_all(vec![1, 2, 3])
599            .expect("Failed to retrieve ents")
600            .iter()
601            .map(|ent| ent.id())
602            .collect::<HashSet<Id>>();
603        assert_eq!(results, [1, 2, 3].iter().copied().collect());
604
605        let results = db
606            .get_all(vec![1, 3])
607            .expect("Failed to retrieve ents")
608            .iter()
609            .map(|ent| ent.id())
610            .collect::<HashSet<Id>>();
611        assert_eq!(results, [1, 3].iter().copied().collect());
612
613        let results = db
614            .get_all(vec![2, 3, 4, 5, 6, 7, 8])
615            .expect("Failed to retrieve ents")
616            .iter()
617            .map(|ent| ent.id())
618            .collect::<HashSet<Id>>();
619        assert_eq!(results, [2, 3].iter().copied().collect());
620    }
621
622    #[test]
623    fn find_all_should_return_no_ents_by_default() {
624        let db = new_test_database();
625
626        let q = Query::default();
627        query_and_assert(&db, q, &[]);
628    }
629
630    #[test]
631    fn find_all_should_support_filtering_by_id() {
632        let db = new_test_database();
633
634        // If ent with id exists, we expect it to be available
635        let q = Query::default().where_id(TP::equals(1));
636        query_and_assert(&db, q, &[1]);
637
638        // If ent with either id exists, we expect it to be available
639        let q = Query::default().where_id(TP::equals(1) | TP::equals(2));
640        query_and_assert(&db, q, &[1, 2]);
641
642        // If ent with id does not exist, we expect empty
643        let q = Query::default().where_id(TP::equals(999));
644        query_and_assert(&db, q, &[]);
645
646        // If already in a pipeline, should only filter the existing ids
647        let q = Query::default()
648            .where_id(TP::equals(1) | TP::equals(2))
649            .where_id(TP::equals(1) | TP::equals(3));
650        query_and_assert(&db, q, &[1]);
651    }
652
653    #[test]
654    fn find_all_should_support_filtering_by_type() {
655        let db = new_test_database();
656        let _ = db.insert(Box::from(TestEnt::new(20))).unwrap();
657        let _ = db.insert(Box::from(TestEnt::new(21))).unwrap();
658        let _ = db.insert(Box::from(TestEnt::new(22))).unwrap();
659
660        // If ent with type exists, we expect it to be available
661        let ts = <UntypedEnt as EntType>::type_str();
662        let q = Query::default().where_type(TP::equals(ts.to_string()));
663        query_and_assert(&db, q, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
664
665        // If ent with either type exists, we expect it to be available
666        let q = Query::default().where_type(TP::or(vec![
667            TP::equals(<UntypedEnt as EntType>::type_str().to_string()),
668            TP::equals(<TestEnt as EntType>::type_str().to_string()),
669        ]));
670        query_and_assert(&db, q, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20, 21, 22]);
671
672        // If ent with type does not exist, we expect empty
673        let q = Query::default().where_type(TP::equals(String::from("unknown")));
674        query_and_assert(&db, q, &[]);
675
676        // If already in a pipeline, should only filter the existing ids
677        let q = Query::default()
678            .where_id(TP::equals(1) | TP::equals(2) | TP::equals(4))
679            .where_type(TP::equals(ts.to_string()));
680        query_and_assert(&db, q, &[1, 2, 4]);
681    }
682
683    #[test]
684    fn find_all_should_support_filtering_by_created_timestamp() {
685        let db = new_test_database();
686
687        // Re-create all ents with enough time split between them for us to
688        // properly test creation time
689        for i in 1..=12 {
690            let ent = UntypedEnt::empty_with_id(i);
691            db.insert(Box::from(ent))
692                .expect(&format!("Failed to replace ent {}", i));
693            std::thread::sleep(std::time::Duration::from_millis(1));
694        }
695
696        // Get all ents created after our third ent
697        let time = db.get(3).unwrap().expect("Missing ent 3").created();
698        let q = Query::default().where_created(TP::greater_than(time));
699        query_and_assert(&db, q, &[4, 5, 6, 7, 8, 9, 10, 11, 12]);
700
701        // If already in a pipeline, should only filter the existing ids
702        let time = db.get(3).unwrap().expect("Missing ent 3").created();
703        let q = Query::default()
704            .where_id(TP::less_than(8))
705            .where_created(TP::greater_than(time));
706        query_and_assert(&db, q, &[4, 5, 6, 7]);
707    }
708
709    #[test]
710    fn find_all_should_support_filtering_by_last_updated_timestamp() {
711        let db = new_test_database();
712
713        // Update all ents with enough time split between them for us to
714        // properly test last updated time
715        for i in (1..=12).rev() {
716            use entity::DatabaseExt;
717            let mut ent = db
718                .get_typed::<UntypedEnt>(i)
719                .unwrap()
720                .expect(&format!("Missing ent {}", i));
721            ent.mark_updated().unwrap();
722            db.insert(Box::from(ent))
723                .expect(&format!("Failed to update ent {}", i));
724            std::thread::sleep(std::time::Duration::from_millis(1));
725        }
726
727        // Get all ents updated after our third ent
728        let time = db.get(3).unwrap().expect("Missing ent 3").last_updated();
729        let q = Query::default().where_last_updated(TP::greater_than(time));
730        query_and_assert(&db, q, &[1, 2]);
731
732        // If already in a pipeline, should only filter the existing ids
733        let time = db.get(3).unwrap().expect("Missing ent 3").created();
734        let q = Query::default()
735            .where_id(TP::equals(2))
736            .where_last_updated(TP::greater_than(time));
737        query_and_assert(&db, q, &[2]);
738    }
739
740    #[test]
741    fn find_all_should_support_filtering_by_field() {
742        let db = new_test_database();
743
744        // If ent's field passes condition, it will be included in return
745        let q = Query::default().where_field("a", P::equals(3));
746        query_and_assert(&db, q, &[5]);
747
748        // If already have ents in pipeline, they will be filtered by "field"
749        let q = Query::default()
750            .where_id(TP::equals(4) | TP::equals(6))
751            .where_field("a", P::greater_than(1));
752        query_and_assert(&db, q, &[6]);
753    }
754
755    #[test]
756    fn find_all_should_support_filtering_by_edge() {
757        let db = new_test_database();
758
759        // If ent's edge passes condition, it will be included in return
760        let q = Query::default().where_edge("a", Filter::Id(TP::equals(3)));
761        query_and_assert(&db, q, &[12]);
762
763        // If already have ents in pipeline, they will be filtered by "edge"
764        let q = Query::default()
765            .where_id(TP::equals(10) | TP::equals(12))
766            .where_edge("a", Filter::Id(TP::always()));
767        query_and_assert(&db, q, &[10, 12]);
768    }
769
770    #[test]
771    fn find_all_should_support_transforming_into_edge() {
772        let db = new_test_database();
773
774        // Will take the ids of each ent with the given edge and use
775        // them going forward; in this example, ents #10 and #11 have
776        // overlapping ids for edge b
777        let q = Query::default().where_into_edge("b");
778        query_and_assert(&db, q, &[1, 2, 3, 4, 5, 6]);
779
780        // If already have ents in pipeline, their edge's ids will
781        // be used specifically; in this example, ent #12 has no ents
782        // for edge b
783        let q = Query::default()
784            .where_id(TP::equals(10) | TP::equals(12))
785            .where_into_edge("b");
786        query_and_assert(&db, q, &[3, 4, 5]);
787    }
788
789    #[derive(Clone, Debug, PartialEq, Eq)]
790    #[cfg_attr(feature = "serde-1", derive(serde::Serialize, serde::Deserialize))]
791    struct TestEnt(Id);
792
793    impl TestEnt {
794        pub fn new(id: Id) -> Self {
795            Self(id)
796        }
797    }
798
799    impl EntType for TestEnt {
800        fn type_data() -> EntTypeData {
801            EntTypeData::Concrete {
802                ty: concat!(module_path!(), "::TestEnt"),
803            }
804        }
805    }
806
807    #[cfg_attr(feature = "serde-1", typetag::serde)]
808    impl Ent for TestEnt {
809        fn id(&self) -> Id {
810            self.0
811        }
812
813        fn set_id(&mut self, id: Id) {
814            self.0 = id;
815        }
816
817        fn r#type(&self) -> &str {
818            Self::type_str()
819        }
820
821        fn created(&self) -> u64 {
822            0
823        }
824
825        fn last_updated(&self) -> u64 {
826            0
827        }
828
829        fn mark_updated(&mut self) -> Result<(), EntMutationError> {
830            Ok(())
831        }
832
833        fn field_definitions(&self) -> Vec<FieldDefinition> {
834            Vec::new()
835        }
836
837        fn field_names(&self) -> Vec<String> {
838            Vec::new()
839        }
840
841        fn field(&self, _name: &str) -> Option<Value> {
842            None
843        }
844
845        fn update_field(&mut self, name: &str, _value: Value) -> Result<Value, EntMutationError> {
846            Err(EntMutationError::NoField {
847                name: name.to_string(),
848            })
849        }
850
851        fn edge_definitions(&self) -> Vec<EdgeDefinition> {
852            Vec::new()
853        }
854
855        fn edge_names(&self) -> Vec<String> {
856            Vec::new()
857        }
858
859        fn edge(&self, _name: &str) -> Option<EdgeValue> {
860            None
861        }
862
863        fn update_edge(
864            &mut self,
865            name: &str,
866            _value: EdgeValue,
867        ) -> Result<EdgeValue, EntMutationError> {
868            Err(EntMutationError::NoEdge {
869                name: name.to_string(),
870            })
871        }
872
873        fn connect(&mut self, _database: WeakDatabaseRc) {}
874
875        fn disconnect(&mut self) {}
876
877        fn is_connected(&self) -> bool {
878            false
879        }
880
881        fn load_edge(&self, _name: &str) -> DatabaseResult<Vec<Box<dyn Ent>>> {
882            Err(DatabaseError::Disconnected)
883        }
884
885        fn clear_cache(&mut self) {}
886
887        fn refresh(&mut self) -> DatabaseResult<()> {
888            Err(DatabaseError::Disconnected)
889        }
890
891        fn commit(&mut self) -> DatabaseResult<()> {
892            Err(DatabaseError::Disconnected)
893        }
894
895        fn remove(&self) -> DatabaseResult<bool> {
896            Err(DatabaseError::Disconnected)
897        }
898    }
899}