Skip to main content

mentat/
store.rs

1// Copyright 2016-2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11#![allow(dead_code)]
12
13use std::collections::BTreeMap;
14
15use std::sync::Arc;
16
17use core_traits::{Entid, StructuredMap, TypedValue};
18
19use mentat_core::{HasSchema, Keyword, TxReport, ValueRc};
20use mentat_db::TxObserver;
21
22use mentat_transaction::{
23    CacheAction, CacheDirection, InProgress, InProgressRead, Pullable, Queryable,
24};
25
26use crate::conn::Conn;
27
28use public_traits::errors::Result;
29
30use mentat_transaction::query::{PreparedResult, QueryExplanation, QueryInputs, QueryOutput};
31
32/// A convenience wrapper around a single SQLite connection and a Conn. This is suitable
33/// for applications that don't require complex connection management.
34/// The `:db/fn` attribute: the mino source of a user transaction function.
35const DB_FN_ATTRIBUTE: &str =
36    "[{:db/ident :db/fn :db/valueType :db.type/string :db/cardinality :db.cardinality/one}]";
37
38/// The live `:db/fn` source on the entity whose `:db/ident` is `ident`, if any.
39#[cfg(feature = "mino")]
40pub(crate) fn db_fn_source(
41    store: &Store,
42    ident: &str,
43) -> std::result::Result<Option<String>, String> {
44    let db_fn = Keyword::namespaced("db", "fn");
45    let schema = store.conn.current_schema();
46    if schema.get_entid(&db_fn).is_none() {
47        return Ok(None);
48    }
49    let Ok(edn::Value::Keyword(kw)) = edn::parse::value(ident).map(|v| v.without_spans()) else {
50        return Ok(None);
51    };
52    let Some(e) = schema.get_entid(&kw) else {
53        return Ok(None); // not an ident at all
54    };
55    match store
56        .lookup_value_for_attribute(e.0, &db_fn)
57        .map_err(|err| err.to_string())?
58    {
59        Some(TypedValue::String(s)) => Ok(Some(s.as_ref().clone())),
60        _ => Ok(None),
61    }
62}
63
64pub struct Store {
65    conn: Conn,
66    sqlite: rusqlite::Connection,
67    advisor: std::cell::RefCell<crate::auto_index::Advisor>,
68}
69
70impl Store {
71    /// Open a store at the supplied path, ensuring that it includes the bootstrap schema.
72    pub fn open(path: &str) -> Result<Store> {
73        let mut connection = crate::new_connection(path)?;
74        let conn = Conn::connect(&mut connection)?;
75        Ok(Store {
76            conn,
77            sqlite: connection,
78            advisor: Default::default(),
79        })
80    }
81
82    pub fn transact(&mut self, transaction: &str) -> Result<TxReport> {
83        let transaction = self.expand_tx_fns(transaction)?;
84        let mut ip = self.begin_transaction()?;
85        let report = ip.transact(transaction.as_str())?;
86        ip.commit()?;
87        Ok(report)
88    }
89
90    /// Prepare `transaction` for the transactor: replace every invocation of a
91    /// user-defined transaction function (`[:my/fn arg ...]` on an ident that
92    /// carries `:db/fn`) with the tx-data it returns, and define the `:db/fn`
93    /// attribute itself the first time a transaction installs a function.
94    /// [`Store::transact`] and [`Store::with_speculative`] call this; a host
95    /// that drives [`InProgress::transact`] directly calls it first.
96    ///
97    /// Functions run in the sandboxed mino interpreter (`mino` feature), see
98    /// the database as of the start of the transaction, and nest at most
99    /// `MENTAT_TXFN_MAX_DEPTH` (default 16) deep. Without the `mino` feature
100    /// only the `:db/fn` attribute bootstrap applies.
101    pub fn expand_tx_fns(&mut self, transaction: &str) -> Result<String> {
102        let has_db_fn_attr = self
103            .conn
104            .current_schema()
105            .get_entid(&Keyword::namespaced("db", "fn"))
106            .is_some();
107        if !has_db_fn_attr && transaction.contains(":db/fn") {
108            // The embedded core schema predates transaction functions; define
109            // `:db/fn` alongside the first function installed, rather than
110            // changing every store's bootstrap.
111            self.transact_raw(DB_FN_ATTRIBUTE)?;
112        }
113        // Only a store that has (or is about to have) `:db/fn` can hold a
114        // transaction function, so other stores skip the interpreter entirely.
115        #[cfg(feature = "mino")]
116        if has_db_fn_attr || transaction.contains(":db/fn") {
117            // ponytail: swaps in a throwaway in-memory Store (~ms) to move this
118            // one into the script backend; only paid by transactions that could
119            // invoke a function. Borrowing it instead needs a non-'static
120            // ScriptBackend.
121            let store = std::mem::replace(self, Store::open("")?);
122            let (store, expanded) = crate::script::expand_tx_fns(store, transaction);
123            *self = store;
124            return expanded.map_err(public_traits::errors::MentatError::TransactionFunction);
125        }
126        Ok(transaction.to_string())
127    }
128
129    /// Transact without transaction-function expansion.
130    fn transact_raw(&mut self, transaction: &str) -> Result<TxReport> {
131        let mut ip = self.begin_transaction()?;
132        let report = ip.transact(transaction)?;
133        ip.commit()?;
134        Ok(report)
135    }
136
137    /// Speculatively transact `transaction` and return its `TxReport` WITHOUT
138    /// committing (Datomic `d/with`). Delegates to [`Conn::transact_speculative`],
139    /// which opens a real SQLite transaction and rolls it back on drop, so the
140    /// store is left unchanged. This is the thin `&mut Store` wrapper the
141    /// scripting layer needs, since `transact_speculative` lives on `Conn` and
142    /// wants both the `Conn` and the `rusqlite::Connection` (both private here).
143    pub fn with_speculative(&mut self, transaction: &str) -> Result<TxReport> {
144        let transaction = self.expand_tx_fns(transaction)?;
145        self.conn
146            .transact_speculative(&mut self.sqlite, transaction.as_str())
147    }
148}
149
150#[cfg(feature = "sqlcipher")]
151impl Store {
152    /// Variant of `open` that allows a key (for encryption/decryption) to be
153    /// supplied. Fails unless linked against sqlcipher (or something else that
154    /// supports the Sqlite Encryption Extension).
155    pub fn open_with_key(path: &str, encryption_key: &str) -> Result<Store> {
156        let mut connection = crate::new_connection_with_key(path, encryption_key)?;
157        let conn = Conn::connect(&mut connection)?;
158        Ok(Store {
159            conn,
160            sqlite: connection,
161            advisor: Default::default(),
162        })
163    }
164
165    /// Change the key for a database that was opened using `open_with_key` (using `PRAGMA
166    /// rekey`). Fails unless linked against sqlcipher (or something else that supports the Sqlite
167    /// Encryption Extension).
168    pub fn change_encryption_key(&mut self, new_encryption_key: &str) -> Result<()> {
169        crate::change_encryption_key(&self.sqlite, new_encryption_key)?;
170        Ok(())
171    }
172}
173
174impl Store {
175    /// Intended for use from tests.
176    pub fn sqlite_mut(&mut self) -> &mut rusqlite::Connection {
177        &mut self.sqlite
178    }
179
180    /// A shared borrow of the underlying SQLite connection, for read-only
181    /// helpers (e.g. `mentat_db::debug::datoms`) that only need `&Connection`.
182    pub fn sqlite_ref(&self) -> &rusqlite::Connection {
183        &self.sqlite
184    }
185
186    #[cfg(test)]
187    pub fn is_registered_as_observer(&self, key: &str) -> bool {
188        self.conn
189            .tx_observer_service
190            .lock()
191            .unwrap()
192            .is_registered(key)
193    }
194}
195
196impl Store {
197    pub fn dismantle(self) -> (rusqlite::Connection, Conn) {
198        (self.sqlite, self.conn)
199    }
200
201    pub fn conn(&self) -> &Conn {
202        &self.conn
203    }
204
205    pub fn begin_read(&mut self) -> Result<InProgressRead<'_, '_>> {
206        self.conn.begin_read(&mut self.sqlite)
207    }
208
209    pub fn begin_transaction(&mut self) -> Result<InProgress<'_, '_>> {
210        self.conn.begin_transaction(&mut self.sqlite)
211    }
212
213    pub fn cache(&mut self, attr: &Keyword, direction: CacheDirection) -> Result<()> {
214        let schema = &self.conn.current_schema();
215        self.conn.cache(
216            &mut self.sqlite,
217            schema,
218            attr,
219            direction,
220            CacheAction::Register,
221        )
222    }
223
224    pub fn register_observer(&mut self, key: String, observer: Arc<TxObserver>) {
225        self.conn.register_observer(key, observer);
226    }
227
228    pub fn unregister_observer(&mut self, key: &str) {
229        self.conn.unregister_observer(key);
230    }
231
232    pub fn last_tx_id(&self) -> Entid {
233        self.conn.last_tx_id()
234    }
235
236    /// How mentat manages secondary indexes for this `Store` (see [`AutoIndex`]).
237    /// The default comes from `MENTAT_AUTO_INDEX` (`off`/`schema`/`adaptive`),
238    /// else `Schema`.
239    ///
240    /// [`AutoIndex`]: crate::AutoIndex
241    pub fn set_auto_index(&mut self, mode: crate::AutoIndex) {
242        self.advisor.get_mut().mode = mode;
243    }
244
245    /// Adaptive thresholds: create an index once an attribute is value-filtered
246    /// by `min_uses` queries within one tuning period; drop a mentat-created
247    /// index unused for a whole period and for at least `idle`.
248    pub fn set_index_tuning(&mut self, min_uses: u32, idle: std::time::Duration) {
249        let a = self.advisor.get_mut();
250        a.min_uses = min_uses.max(1);
251        a.idle = idle;
252    }
253
254    /// Apply (or, with `dry_run`, only report) index changes per the current
255    /// [`AutoIndex`](crate::AutoIndex) mode. Only ever drops indexes mentat
256    /// created (listed in `mentat_managed_indexes`).
257    pub fn tune_indexes(&mut self, dry_run: bool) -> Result<Vec<crate::IndexAction>> {
258        self.tune(dry_run)
259    }
260
261    fn tune(&self, dry_run: bool) -> Result<Vec<crate::IndexAction>> {
262        let schema = self.conn.current_schema();
263        self.advisor
264            .borrow_mut()
265            .tune(&self.sqlite, &schema, dry_run)
266    }
267
268    /// Run a Datalog query against a historical basis reconstructed as of
269    /// transaction `tx` (inclusive) -- the state that was current at `tx`.
270    pub fn q_once_as_of<T>(&self, query: &str, inputs: T, tx: Entid) -> Result<QueryOutput>
271    where
272        T: Into<Option<QueryInputs>>,
273    {
274        self.conn.q_once_as_of(&self.sqlite, query, inputs, tx)
275    }
276
277    /// `EXPLAIN QUERY PLAN` for a query, optionally against a historical basis.
278    pub fn q_explain_temporal<T>(
279        &self,
280        query: &str,
281        inputs: T,
282        temporal: Option<crate::TemporalBound>,
283    ) -> Result<QueryExplanation>
284    where
285        T: Into<Option<QueryInputs>>,
286    {
287        self.conn
288            .q_explain_temporal(&self.sqlite, query, inputs, temporal)
289    }
290
291    /// Run a Datalog query seeing only datoms transacted after transaction `tx`
292    /// (`since`). Typically used with a history pattern `[?e ?a ?v ?tx ?added]`.
293    pub fn q_once_since<T>(&self, query: &str, inputs: T, tx: Entid) -> Result<QueryOutput>
294    where
295        T: Into<Option<QueryInputs>>,
296    {
297        self.conn.q_once_since(&self.sqlite, query, inputs, tx)
298    }
299}
300
301impl Queryable for Store {
302    fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
303    where
304        T: Into<Option<QueryInputs>>,
305    {
306        if self.advisor.borrow().mode != crate::AutoIndex::Adaptive {
307            return self.conn.q_once(&self.sqlite, query, inputs);
308        }
309        let (out, attrs) = self
310            .conn
311            .q_once_value_filtered(&self.sqlite, query, inputs)?;
312        if self.advisor.borrow_mut().record(&attrs) {
313            // Best effort: e.g. another connection may hold the write lock.
314            if let Err(e) = self.tune(false) {
315                log::warn!("automatic index tuning failed: {e}");
316            }
317        }
318        Ok(out)
319    }
320
321    fn q_prepare<T>(&self, query: &str, inputs: T) -> PreparedResult<'_>
322    where
323        T: Into<Option<QueryInputs>>,
324    {
325        self.conn.q_prepare(&self.sqlite, query, inputs)
326    }
327
328    fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
329    where
330        T: Into<Option<QueryInputs>>,
331    {
332        self.conn.q_explain(&self.sqlite, query, inputs)
333    }
334
335    fn lookup_values_for_attribute<E>(
336        &self,
337        entity: E,
338        attribute: &edn::Keyword,
339    ) -> Result<Vec<TypedValue>>
340    where
341        E: Into<Entid>,
342    {
343        self.conn
344            .lookup_values_for_attribute(&self.sqlite, entity.into(), attribute)
345    }
346
347    fn lookup_value_for_attribute<E>(
348        &self,
349        entity: E,
350        attribute: &edn::Keyword,
351    ) -> Result<Option<TypedValue>>
352    where
353        E: Into<Entid>,
354    {
355        self.conn
356            .lookup_value_for_attribute(&self.sqlite, entity.into(), attribute)
357    }
358}
359
360impl Pullable for Store {
361    fn pull_attributes_for_entities<E, A>(
362        &self,
363        entities: E,
364        attributes: A,
365    ) -> Result<BTreeMap<Entid, ValueRc<StructuredMap>>>
366    where
367        E: IntoIterator<Item = Entid>,
368        A: IntoIterator<Item = Entid>,
369    {
370        self.conn
371            .pull_attributes_for_entities(&self.sqlite, entities, attributes)
372    }
373
374    fn pull_attributes_for_entity<A>(&self, entity: Entid, attributes: A) -> Result<StructuredMap>
375    where
376        A: IntoIterator<Item = Entid>,
377    {
378        self.conn
379            .pull_attributes_for_entity(&self.sqlite, entity, attributes)
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    use uuid::Uuid;
388
389    use std::collections::BTreeSet;
390    use std::path::{Path, PathBuf};
391    use std::sync::mpsc;
392    use std::sync::Mutex;
393    use std::time::Duration;
394
395    use mentat_db::cache::SQLiteAttributeCache;
396
397    use core_traits::{TypedValue, ValueType};
398
399    use mentat_core::{CachedAttributes, HasSchema};
400
401    use mentat_transaction::entity_builder::BuildTerms;
402
403    use mentat_transaction::query::PreparedQuery;
404
405    use mentat_query_algebrizer::QueryInputs;
406
407    use crate::vocabulary::{AttributeBuilder, Definition, VersionedStore};
408
409    use core_traits::attribute::Unique;
410
411    fn fixture_path(rest: &str) -> PathBuf {
412        let fixtures = Path::new("../fixtures/");
413        fixtures.join(Path::new(rest))
414    }
415
416    #[test]
417    fn test_prepared_query_with_cache() {
418        let mut store = Store::open("").expect("opened");
419        let mut in_progress = store.begin_transaction().expect("began");
420        in_progress
421            .import(fixture_path("cities.schema"))
422            .expect("transacted schema");
423        in_progress
424            .import(fixture_path("all_seattle.edn"))
425            .expect("transacted data");
426        in_progress
427            .cache(
428                &kw!(:neighborhood/district),
429                CacheDirection::Forward,
430                CacheAction::Register,
431            )
432            .expect("cache done");
433        in_progress
434            .cache(
435                &kw!(:district/name),
436                CacheDirection::Forward,
437                CacheAction::Register,
438            )
439            .expect("cache done");
440        in_progress
441            .cache(
442                &kw!(:neighborhood/name),
443                CacheDirection::Reverse,
444                CacheAction::Register,
445            )
446            .expect("cache done");
447
448        let query = r#"[:find ?district
449                        :in ?hood
450                        :where
451                        [?neighborhood :neighborhood/name ?hood]
452                        [?neighborhood :neighborhood/district ?d]
453                        [?d :district/name ?district]]"#;
454        let hood = "Beacon Hill";
455        let inputs =
456            QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::typed_string(hood))]);
457        let mut prepared = in_progress.q_prepare(query, inputs).expect("prepared");
458        match prepared {
459            PreparedQuery::Constant {
460                select: ref _select,
461            } => {}
462            _ => panic!(),
463        };
464
465        let start = std::time::Instant::now();
466        let results = prepared.run(None).expect("results");
467        let end = std::time::Instant::now();
468        println!(
469            "Prepared cache execution took {}µs",
470            (end - start).as_micros()
471        );
472        assert_eq!(
473            results.into_rel().expect("result"),
474            vec![vec![TypedValue::typed_string("Greater Duwamish")]].into()
475        );
476    }
477
478    trait StoreCache {
479        fn get_entid_for_value(&self, attr: Entid, val: &TypedValue) -> Option<Entid>;
480        fn is_attribute_cached_reverse(&self, attr: Entid) -> bool;
481        fn is_attribute_cached_forward(&self, attr: Entid) -> bool;
482    }
483
484    impl StoreCache for Store {
485        fn get_entid_for_value(&self, attr: Entid, val: &TypedValue) -> Option<Entid> {
486            let cache = self.conn.current_cache();
487            cache.get_entid_for_value(attr, val)
488        }
489
490        fn is_attribute_cached_forward(&self, attr: Entid) -> bool {
491            self.conn.current_cache().is_attribute_cached_forward(attr)
492        }
493
494        fn is_attribute_cached_reverse(&self, attr: Entid) -> bool {
495            self.conn.current_cache().is_attribute_cached_reverse(attr)
496        }
497    }
498
499    #[test]
500    fn test_cache_mutation() {
501        let mut store = Store::open("").expect("opened");
502
503        {
504            let mut in_progress = store.begin_transaction().expect("begun");
505            in_progress
506                .transact(
507                    r#"[
508                {  :db/ident       :foo/bar
509                   :db/cardinality :db.cardinality/one
510                   :db/index       true
511                   :db/unique      :db.unique/identity
512                   :db/valueType   :db.type/long },
513                {  :db/ident       :foo/baz
514                   :db/cardinality :db.cardinality/one
515                   :db/valueType   :db.type/boolean }
516                {  :db/ident       :foo/x
517                   :db/cardinality :db.cardinality/many
518                   :db/valueType   :db.type/long }]"#,
519                )
520                .expect("transact");
521
522            // Cache one….
523            in_progress
524                .cache(
525                    &kw!(:foo/bar),
526                    CacheDirection::Reverse,
527                    CacheAction::Register,
528                )
529                .expect("cache done");
530            in_progress.commit().expect("commit");
531        }
532
533        let foo_bar = store
534            .conn
535            .current_schema()
536            .get_entid(&kw!(:foo/bar))
537            .expect("foo/bar")
538            .0;
539        let foo_baz = store
540            .conn
541            .current_schema()
542            .get_entid(&kw!(:foo/baz))
543            .expect("foo/baz")
544            .0;
545        let foo_x = store
546            .conn
547            .current_schema()
548            .get_entid(&kw!(:foo/x))
549            .expect("foo/x")
550            .0;
551
552        // … and cache the others via the store.
553        store
554            .cache(&kw!(:foo/baz), CacheDirection::Both)
555            .expect("cache done");
556        store
557            .cache(&kw!(:foo/x), CacheDirection::Forward)
558            .expect("cache done");
559        {
560            assert!(store.is_attribute_cached_reverse(foo_bar));
561            assert!(store.is_attribute_cached_forward(foo_baz));
562            assert!(store.is_attribute_cached_reverse(foo_baz));
563            assert!(store.is_attribute_cached_forward(foo_x));
564        }
565
566        // Add some data.
567        {
568            let mut in_progress = store.begin_transaction().expect("begun");
569
570            {
571                assert!(in_progress.cache.is_attribute_cached_reverse(foo_bar));
572                assert!(in_progress.cache.is_attribute_cached_forward(foo_baz));
573                assert!(in_progress.cache.is_attribute_cached_reverse(foo_baz));
574                assert!(in_progress.cache.is_attribute_cached_forward(foo_x));
575
576                assert!(in_progress
577                    .cache
578                    .overlay
579                    .is_attribute_cached_reverse(foo_bar));
580                assert!(in_progress
581                    .cache
582                    .overlay
583                    .is_attribute_cached_forward(foo_baz));
584                assert!(in_progress
585                    .cache
586                    .overlay
587                    .is_attribute_cached_reverse(foo_baz));
588                assert!(in_progress.cache.overlay.is_attribute_cached_forward(foo_x));
589            }
590
591            in_progress
592                .transact(
593                    r#"[
594                {:foo/bar 15, :foo/baz false, :foo/x [1, 2, 3]}
595                {:foo/bar 99, :foo/baz true}
596                {:foo/bar -2, :foo/baz true}
597                ]"#,
598                )
599                .expect("transact");
600
601            // Data is in the cache.
602            let first = in_progress
603                .cache
604                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
605                .expect("id");
606            assert_eq!(
607                in_progress
608                    .cache
609                    .get_value_for_entid(&in_progress.schema, foo_baz, first)
610                    .expect("val"),
611                &TypedValue::Boolean(false)
612            );
613
614            // All three values for :foo/x.
615            let all_three: BTreeSet<TypedValue> = in_progress
616                .cache
617                .get_values_for_entid(&in_progress.schema, foo_x, first)
618                .expect("val")
619                .iter()
620                .cloned()
621                .collect();
622            assert_eq!(
623                all_three,
624                vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()
625            );
626
627            in_progress.commit().expect("commit");
628        }
629
630        // Data is still in the cache.
631        {
632            let first = store
633                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
634                .expect("id");
635            let cache: SQLiteAttributeCache = store.conn.current_cache();
636            assert_eq!(
637                cache
638                    .get_value_for_entid(&store.conn.current_schema(), foo_baz, first)
639                    .expect("val"),
640                &TypedValue::Boolean(false)
641            );
642
643            let all_three: BTreeSet<TypedValue> = cache
644                .get_values_for_entid(&store.conn.current_schema(), foo_x, first)
645                .expect("val")
646                .iter()
647                .cloned()
648                .collect();
649            assert_eq!(
650                all_three,
651                vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()
652            );
653        }
654
655        // We can remove data and the cache reflects it, immediately and after commit.
656        {
657            let mut in_progress = store.begin_transaction().expect("began");
658            let first = in_progress
659                .cache
660                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
661                .expect("id");
662            in_progress
663                .transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str())
664                .expect("transact");
665
666            let only_two: BTreeSet<TypedValue> = in_progress
667                .cache
668                .get_values_for_entid(&in_progress.schema, foo_x, first)
669                .expect("val")
670                .iter()
671                .cloned()
672                .collect();
673            assert_eq!(
674                only_two,
675                vec![1, 3].into_iter().map(TypedValue::Long).collect()
676            );
677
678            // Rollback: unchanged.
679        }
680        {
681            let first = store
682                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
683                .expect("id");
684            let cache: SQLiteAttributeCache = store.conn.current_cache();
685            assert_eq!(
686                cache
687                    .get_value_for_entid(&store.conn.current_schema(), foo_baz, first)
688                    .expect("val"),
689                &TypedValue::Boolean(false)
690            );
691
692            let all_three: BTreeSet<TypedValue> = cache
693                .get_values_for_entid(&store.conn.current_schema(), foo_x, first)
694                .expect("val")
695                .iter()
696                .cloned()
697                .collect();
698            assert_eq!(
699                all_three,
700                vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()
701            );
702        }
703
704        // Try again, but this time commit.
705        {
706            let mut in_progress = store.begin_transaction().expect("began");
707            let first = in_progress
708                .cache
709                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
710                .expect("id");
711            in_progress
712                .transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str())
713                .expect("transact");
714            in_progress.commit().expect("committed");
715        }
716        {
717            let first = store
718                .get_entid_for_value(foo_bar, &TypedValue::Long(15))
719                .expect("id");
720            let cache: SQLiteAttributeCache = store.conn.current_cache();
721            assert_eq!(
722                cache
723                    .get_value_for_entid(&store.conn.current_schema(), foo_baz, first)
724                    .expect("val"),
725                &TypedValue::Boolean(false)
726            );
727
728            let only_two: BTreeSet<TypedValue> = cache
729                .get_values_for_entid(&store.conn.current_schema(), foo_x, first)
730                .expect("val")
731                .iter()
732                .cloned()
733                .collect();
734            assert_eq!(
735                only_two,
736                vec![1, 3].into_iter().map(TypedValue::Long).collect()
737            );
738        }
739    }
740
741    fn test_register_observer() {
742        let mut conn = Store::open("").unwrap();
743
744        let key = "Test Observer".to_string();
745        let tx_observer = TxObserver::new(BTreeSet::new(), move |_obs_key, _batch| {});
746
747        conn.register_observer(key.clone(), Arc::new(tx_observer));
748        assert!(conn.is_registered_as_observer(&key));
749    }
750
751    #[test]
752    fn test_deregister_observer() {
753        let mut conn = Store::open("").unwrap();
754
755        let key = "Test Observer".to_string();
756
757        let tx_observer = TxObserver::new(BTreeSet::new(), move |_obs_key, _batch| {});
758
759        conn.register_observer(key.clone(), Arc::new(tx_observer));
760        assert!(conn.is_registered_as_observer(&key));
761
762        conn.unregister_observer(&key);
763
764        assert!(!conn.is_registered_as_observer(&key));
765    }
766
767    fn add_schema(conn: &mut Store) {
768        // transact some schema
769        let mut in_progress = conn.begin_transaction().expect("expected in progress");
770        in_progress
771            .ensure_vocabulary(&Definition::new(
772                kw!(:todo/items),
773                1,
774                vec![
775                    (
776                        kw!(:todo/uuid),
777                        AttributeBuilder::helpful()
778                            .value_type(ValueType::Uuid)
779                            .multival(false)
780                            .unique(Unique::Value)
781                            .index(true)
782                            .build(),
783                    ),
784                    (
785                        kw!(:todo/name),
786                        AttributeBuilder::helpful()
787                            .value_type(ValueType::String)
788                            .multival(false)
789                            .fulltext(true)
790                            .build(),
791                    ),
792                    (
793                        kw!(:todo/completion_date),
794                        AttributeBuilder::helpful()
795                            .value_type(ValueType::Instant)
796                            .multival(false)
797                            .build(),
798                    ),
799                    (
800                        kw!(:label/name),
801                        AttributeBuilder::helpful()
802                            .value_type(ValueType::String)
803                            .multival(false)
804                            .unique(Unique::Value)
805                            .fulltext(true)
806                            .index(true)
807                            .build(),
808                    ),
809                    (
810                        kw!(:label/color),
811                        AttributeBuilder::helpful()
812                            .value_type(ValueType::String)
813                            .multival(false)
814                            .build(),
815                    ),
816                ],
817            ))
818            .expect("expected vocubulary");
819        in_progress.commit().expect("Expected vocabulary committed");
820    }
821
822    #[derive(Default, Debug)]
823    struct ObserverOutput {
824        txids: Vec<i64>,
825        changes: Vec<BTreeSet<i64>>,
826        called_key: Option<String>,
827    }
828
829    #[test]
830    fn test_observer_notified_on_registered_change() {
831        let mut conn = Store::open("").unwrap();
832        add_schema(&mut conn);
833
834        let name_entid: Entid = conn
835            .conn()
836            .current_schema()
837            .get_entid(&kw!(:todo/name))
838            .expect("entid to exist for name")
839            .into();
840        let date_entid: Entid = conn
841            .conn()
842            .current_schema()
843            .get_entid(&kw!(:todo/completion_date))
844            .expect("entid to exist for completion_date")
845            .into();
846        let mut registered_attrs = BTreeSet::new();
847        registered_attrs.insert(name_entid);
848        registered_attrs.insert(date_entid);
849
850        let key = "Test Observing".to_string();
851
852        let output = Arc::new(Mutex::new(ObserverOutput::default()));
853
854        let mut_output = Arc::downgrade(&output);
855        let (tx, rx): (mpsc::Sender<()>, mpsc::Receiver<()>) = mpsc::channel();
856        // because the TxObserver is in an Arc and is therefore Sync, we have to wrap the Sender in a Mutex to also
857        // make it Sync.
858        let thread_tx = Mutex::new(tx);
859        let tx_observer = Arc::new(TxObserver::new(registered_attrs, move |obs_key, batch| {
860            if let Some(out) = mut_output.upgrade() {
861                let mut o = out.lock().unwrap();
862                o.called_key = Some(obs_key.to_string());
863                for (tx_id, changes) in batch.into_iter() {
864                    o.txids.push(*tx_id);
865                    o.changes.push(changes.clone());
866                }
867                o.txids.sort_unstable();
868            }
869            thread_tx.lock().unwrap().send(()).unwrap();
870        }));
871
872        conn.register_observer(key.clone(), Arc::clone(&tx_observer));
873        assert!(conn.is_registered_as_observer(&key));
874
875        let mut tx_ids = Vec::new();
876        let mut changesets = Vec::new();
877        let db_tx_instant_entid: Entid = conn
878            .conn()
879            .current_schema()
880            .get_entid(&kw!(:db/txInstant))
881            .expect("entid to exist for :db/txInstant")
882            .into();
883        let uuid_entid: Entid = conn
884            .conn()
885            .current_schema()
886            .get_entid(&kw!(:todo/uuid))
887            .expect("entid to exist for name")
888            .into();
889        {
890            let mut in_progress = conn.begin_transaction().expect("expected transaction");
891            for i in 0..3 {
892                let mut changeset = BTreeSet::new();
893                changeset.insert(db_tx_instant_entid);
894                let name = format!("todo{}", i);
895                let uuid = Uuid::new_v4();
896                let mut builder = in_progress.builder().describe_tempid(&name);
897                builder
898                    .add(kw!(:todo/uuid), TypedValue::Uuid(uuid))
899                    .expect("Expected added uuid");
900                changeset.insert(uuid_entid);
901                builder
902                    .add(kw!(:todo/name), TypedValue::typed_string(name))
903                    .expect("Expected added name");
904                changeset.insert(name_entid);
905                if i % 2 == 0 {
906                    builder
907                        .add(kw!(:todo/completion_date), TypedValue::current_instant())
908                        .expect("Expected added date");
909                    changeset.insert(date_entid);
910                }
911                let (ip, r) = builder.transact();
912                let report = r.expect("expected a report");
913                tx_ids.push(report.tx_id);
914                changesets.push(changeset);
915                in_progress = ip;
916            }
917            let mut builder = in_progress.builder().describe_tempid("Label");
918            builder
919                .add(kw!(:label/name), TypedValue::typed_string("Label 1"))
920                .expect("Expected added name");
921            builder
922                .add(kw!(:label/color), TypedValue::typed_string("blue"))
923                .expect("Expected added color");
924            builder.commit().expect("expect transaction to occur");
925        }
926
927        let delay = Duration::from_millis(100);
928        let _ = rx.recv_timeout(delay);
929
930        let out = Arc::try_unwrap(output).expect("unwrapped");
931        let o = out.into_inner().expect("Expected an Output");
932        assert_eq!(o.called_key, Some(key));
933        assert_eq!(o.txids, tx_ids);
934        assert_eq!(o.changes, changesets);
935    }
936
937    #[test]
938    fn test_observer_not_notified_on_unregistered_change() {
939        let mut conn = Store::open("").unwrap();
940        add_schema(&mut conn);
941
942        let name_entid: Entid = conn
943            .conn()
944            .current_schema()
945            .get_entid(&kw!(:todo/name))
946            .expect("entid to exist for name")
947            .into();
948        let date_entid: Entid = conn
949            .conn()
950            .current_schema()
951            .get_entid(&kw!(:todo/completion_date))
952            .expect("entid to exist for completion_date")
953            .into();
954        let mut registered_attrs = BTreeSet::new();
955        registered_attrs.insert(name_entid);
956        registered_attrs.insert(date_entid);
957
958        let key = "Test Observing".to_string();
959
960        let output = Arc::new(Mutex::new(ObserverOutput::default()));
961
962        let mut_output = Arc::downgrade(&output);
963        let (tx, rx): (mpsc::Sender<()>, mpsc::Receiver<()>) = mpsc::channel();
964        let thread_tx = Mutex::new(tx);
965        let tx_observer = Arc::new(TxObserver::new(registered_attrs, move |obs_key, batch| {
966            if let Some(out) = mut_output.upgrade() {
967                let mut o = out.lock().unwrap();
968                o.called_key = Some(obs_key.to_string());
969                for (tx_id, changes) in batch.into_iter() {
970                    o.txids.push(*tx_id);
971                    o.changes.push(changes.clone());
972                }
973                o.txids.sort_unstable();
974            }
975            thread_tx.lock().unwrap().send(()).unwrap();
976        }));
977
978        conn.register_observer(key.clone(), Arc::clone(&tx_observer));
979        assert!(conn.is_registered_as_observer(&key));
980
981        let tx_ids = Vec::<Entid>::new();
982        let changesets = Vec::<BTreeSet<Entid>>::new();
983        {
984            let mut in_progress = conn.begin_transaction().expect("expected transaction");
985            for i in 0..3 {
986                let name = format!("label{}", i);
987                let mut builder = in_progress.builder().describe_tempid(&name);
988                builder
989                    .add(kw!(:label/name), TypedValue::typed_string(name))
990                    .expect("Expected added name");
991                builder
992                    .add(kw!(:label/color), TypedValue::typed_string("blue"))
993                    .expect("Expected added color");
994                let (ip, _) = builder.transact();
995                in_progress = ip;
996            }
997        }
998
999        let delay = Duration::from_millis(100);
1000        let _ = rx.recv_timeout(delay);
1001
1002        let out = Arc::try_unwrap(output).expect("unwrapped");
1003        let o = out.into_inner().expect("Expected an Output");
1004        assert_eq!(o.called_key, None);
1005        assert_eq!(o.txids, tx_ids);
1006        assert_eq!(o.changes, changesets);
1007    }
1008}