1#![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
32const DB_FN_ATTRIBUTE: &str =
36 "[{:db/ident :db/fn :db/valueType :db.type/string :db/cardinality :db.cardinality/one}]";
37
38#[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); };
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 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 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 self.transact_raw(DB_FN_ATTRIBUTE)?;
112 }
113 #[cfg(feature = "mino")]
116 if has_db_fn_attr || transaction.contains(":db/fn") {
117 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 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 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 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 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 pub fn sqlite_mut(&mut self) -> &mut rusqlite::Connection {
177 &mut self.sqlite
178 }
179
180 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 pub fn set_auto_index(&mut self, mode: crate::AutoIndex) {
242 self.advisor.get_mut().mode = mode;
243 }
244
245 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 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 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 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 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 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 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 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 {
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 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 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 {
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 {
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 }
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 {
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 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 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}