1use crate::{
78 index::{unordered::Index, Unordered as _},
79 journal::contiguous::{
80 variable::{Config as JournalConfig, Journal},
81 Contiguous, Mutable as _,
82 },
83 merkle::mmr::Location,
84 qmdb::{
85 any::{
86 unordered::{variable::Operation, Update},
87 VariableValue,
88 },
89 build_snapshot_from_log, delete_key,
90 operation::{Committable as _, Key, Operation as _},
91 update_key, FloorHelper,
92 },
93 translator::Translator,
94 Context,
95};
96use commonware_codec::{CodecShared, Read};
97use commonware_macros::boxed;
98use commonware_utils::Array;
99use core::{num::NonZeroUsize, ops::Range};
100use std::collections::BTreeMap;
101use tracing::{debug, warn};
102
103type Error = crate::qmdb::Error<crate::mmr::Family>;
104
105#[derive(Clone)]
107pub struct Config<T: Translator, C> {
108 pub log: JournalConfig<C>,
110
111 pub translator: T,
113
114 pub init_cache_size: Option<NonZeroUsize>,
117}
118
119pub struct Changeset<K: Key, V: CodecShared + Clone> {
121 diff: BTreeMap<K, Option<V>>,
122 metadata: Option<V>,
123}
124
125impl<K: Key, V: CodecShared + Clone> Changeset<K, V> {
126 fn into_parts(self) -> (BTreeMap<K, Option<V>>, Option<V>) {
127 (self.diff, self.metadata)
128 }
129}
130
131impl<K: Key, V: CodecShared + Clone> FromIterator<(K, Option<V>)> for Changeset<K, V> {
132 fn from_iter<TIter: IntoIterator<Item = (K, Option<V>)>>(iter: TIter) -> Self {
133 Self {
134 diff: iter.into_iter().collect(),
135 metadata: None,
136 }
137 }
138}
139
140impl<K: Key, V: CodecShared + Clone, const N: usize> From<[(K, Option<V>); N]> for Changeset<K, V> {
141 fn from(items: [(K, Option<V>); N]) -> Self {
142 items.into_iter().collect()
143 }
144}
145
146pub struct Batch<'a, E, K, V, T>
148where
149 E: Context,
150 K: Array,
151 V: VariableValue,
152 T: Translator,
153{
154 db: &'a Db<E, K, V, T>,
155 diff: BTreeMap<K, Option<V>>,
156}
157
158impl<'a, E, K, V, T> Batch<'a, E, K, V, T>
159where
160 E: Context,
161 K: Array,
162 V: VariableValue,
163 T: Translator,
164{
165 const fn new(db: &'a Db<E, K, V, T>) -> Self {
166 Self {
167 db,
168 diff: BTreeMap::new(),
169 }
170 }
171
172 pub fn finalize(self, metadata: Option<V>) -> Changeset<K, V> {
174 Changeset {
175 diff: self.diff,
176 metadata,
177 }
178 }
179
180 pub async fn get(&self, key: &K) -> Result<Option<V>, Error> {
183 if let Some(value) = self.diff.get(key) {
184 return Ok(value.clone());
185 }
186 self.db.get(key).await
187 }
188
189 pub fn update(mut self, key: K, value: V) -> Self {
191 self.diff.insert(key, Some(value));
192 self
193 }
194
195 pub fn delete(mut self, key: K) -> Self {
197 self.diff.insert(key, None);
198 self
199 }
200}
201
202pub struct Db<E, K, V, T>
204where
205 E: Context,
206 K: Array,
207 V: VariableValue,
208 T: Translator,
209{
210 log: Journal<E, Operation<crate::mmr::Family, K, V>>,
217
218 snapshot: Index<T, Location>,
225
226 active_keys: usize,
228
229 pub inactivity_floor_loc: Location,
232
233 pub last_commit_loc: Location,
235
236 pub steps: u64,
239}
240
241impl<E, K, V, T> Db<E, K, V, T>
242where
243 E: Context,
244 K: Array,
245 V: VariableValue,
246 T: Translator,
247{
248 pub async fn get(&self, key: &K) -> Result<Option<V>, Error> {
250 for &loc in self.snapshot.get(key) {
251 let Operation::Update(Update(k, v)) = self.get_op(loc).await? else {
252 unreachable!("location ({loc}) does not reference update operation");
253 };
254
255 if &k == key {
256 return Ok(Some(v));
257 }
258 }
259
260 Ok(None)
261 }
262
263 pub const fn new_batch(&self) -> Batch<'_, E, K, V, T> {
265 Batch::new(self)
266 }
267
268 pub const fn is_empty(&self) -> bool {
270 self.active_keys == 0
271 }
272
273 async fn get_op(&self, loc: Location) -> Result<Operation<crate::mmr::Family, K, V>, Error> {
277 assert!(*loc < self.log.bounds().end);
278 self.log.read(*loc).await.map_err(|e| match e {
279 crate::journal::Error::ItemPruned(_) => Error::OperationPruned(loc),
280 e => Error::Journal(e),
281 })
282 }
283
284 pub fn bounds(&self) -> std::ops::Range<Location> {
287 let bounds = self.log.bounds();
288 Location::new(bounds.start)..Location::new(bounds.end)
289 }
290
291 pub const fn size(&self) -> Location {
293 Location::new(self.log.size())
294 }
295
296 pub const fn inactivity_floor_loc(&self) -> Location {
299 self.inactivity_floor_loc
300 }
301
302 pub async fn get_metadata(&self) -> Result<Option<V>, Error> {
304 let Operation::CommitFloor(metadata, _) = self.log.read(*self.last_commit_loc).await?
305 else {
306 unreachable!("last commit should be a commit floor operation");
307 };
308
309 Ok(metadata)
310 }
311
312 pub async fn prune(&mut self, prune_loc: Location) -> Result<(), Error> {
318 if prune_loc > self.inactivity_floor_loc {
319 return Err(Error::PruneBeyondMinRequired(
320 prune_loc,
321 self.inactivity_floor_loc,
322 ));
323 }
324
325 self.log.commit().await?;
329
330 if !self.log.prune(*prune_loc).await? {
333 return Ok(());
334 }
335
336 let bounds = self.log.bounds();
337 let log_size = Location::new(bounds.end);
338 let oldest_retained_loc = Location::new(bounds.start);
339 debug!(
340 ?log_size,
341 ?oldest_retained_loc,
342 ?prune_loc,
343 "pruned inactive ops"
344 );
345
346 Ok(())
347 }
348
349 pub async fn init(
351 context: E,
352 cfg: Config<T, <Operation<crate::mmr::Family, K, V> as Read>::Cfg>,
353 ) -> Result<Self, Error> {
354 let mut log =
355 Journal::<E, Operation<crate::mmr::Family, K, V>>::init(context.child("log"), cfg.log)
356 .await?;
357
358 if log.rewind_to(|op| op.is_commit()).await? == 0 {
360 warn!("Log is empty, initializing new db");
361 log.append(&Operation::CommitFloor(None, Location::new(0)))
362 .await?;
363 }
364
365 log.sync().await?;
368
369 let last_commit_loc =
370 Location::new(log.size().checked_sub(1).expect("commit should exist"));
371
372 let cache_size = cfg.init_cache_size;
374 let mut snapshot = Index::new(context.child("snapshot"), cfg.translator);
375 let (inactivity_floor_loc, active_keys) = {
376 let op = log.read(*last_commit_loc).await?;
377 let inactivity_floor_loc = op.has_floor().expect("last op should be a commit");
378 if inactivity_floor_loc > last_commit_loc {
379 return Err(crate::qmdb::Error::DataCorrupted(
380 "inactivity floor exceeds last commit",
381 ));
382 }
383 let active_keys = build_snapshot_from_log(
384 inactivity_floor_loc,
385 &log,
386 &mut snapshot,
387 cache_size,
388 |_, _| {},
389 )
390 .await?;
391 (inactivity_floor_loc, active_keys)
392 };
393
394 Ok(Self {
395 log,
396 snapshot,
397 active_keys,
398 inactivity_floor_loc,
399 last_commit_loc,
400 steps: 0,
401 })
402 }
403
404 pub async fn sync(&mut self) -> Result<(), Error> {
408 self.log.sync().await.map_err(Into::into)
409 }
410
411 #[boxed]
413 pub async fn destroy(self) -> Result<(), Error> {
414 self.log.destroy().await.map_err(Into::into)
415 }
416
417 #[allow(clippy::type_complexity)]
418 const fn as_floor_helper(
419 &mut self,
420 ) -> FloorHelper<
421 '_,
422 crate::mmr::Family,
423 Index<T, Location>,
424 Journal<E, Operation<crate::mmr::Family, K, V>>,
425 > {
426 FloorHelper {
427 snapshot: &mut self.snapshot,
428 log: &mut self.log,
429 }
430 }
431
432 pub async fn apply_batch(&mut self, batch: Changeset<K, V>) -> Result<Range<Location>, Error> {
438 let start_loc = self.last_commit_loc + 1;
439 let (diff, metadata) = batch.into_parts();
440
441 for (key, value) in diff {
442 if let Some(value) = value {
443 let updated = {
444 let new_loc = self.log.bounds().end;
445 update_key::<crate::mmr::Family, _, _>(
446 &mut self.snapshot,
447 &self.log,
448 &key,
449 Location::new(new_loc),
450 None,
451 )
452 .await?
453 };
454 if updated.is_some() {
455 self.steps += 1;
456 } else {
457 self.active_keys += 1;
458 }
459 self.log
460 .append(&Operation::Update(Update(key, value)))
461 .await?;
462 } else {
463 let deleted = delete_key::<crate::mmr::Family, _, _>(
464 &mut self.snapshot,
465 &self.log,
466 &key,
467 None,
468 )
469 .await?;
470 if deleted.is_some() {
471 self.log.append(&Operation::Delete(key)).await?;
472 self.steps += 1;
473 self.active_keys -= 1;
474 }
475 }
476 }
477
478 if self.is_empty() {
481 self.inactivity_floor_loc = self.size();
482 debug!(tip = ?self.inactivity_floor_loc, "db is empty, raising floor to tip");
483 } else {
484 let steps_to_take = self.steps + 1;
485 for _ in 0..steps_to_take {
486 let loc = self.inactivity_floor_loc;
487 self.inactivity_floor_loc = self.as_floor_helper().raise_floor(loc).await?;
488 }
489 }
490
491 self.last_commit_loc = Location::new(
493 self.log
494 .append(&Operation::CommitFloor(metadata, self.inactivity_floor_loc))
495 .await?,
496 );
497
498 self.steps = 0;
499
500 let end_loc = self.size();
501 Ok(start_loc..end_loc)
502 }
503
504 pub async fn commit(&mut self) -> Result<(), Error> {
506 self.log.commit().await.map_err(Into::into)
507 }
508}
509
510#[cfg(test)]
511mod test {
512 use super::*;
513 use crate::translator::TwoCap;
514 use commonware_cryptography::{
515 blake3::{Blake3, Digest},
516 Hasher as _,
517 };
518 use commonware_macros::test_traced;
519 use commonware_math::algebra::Random;
520 use commonware_runtime::{buffer::paged::CacheRef, deterministic, Runner, Supervisor as _};
521 use commonware_utils::{NZUsize, NZU16, NZU64};
522 use std::num::{NonZeroU16, NonZeroUsize};
523
524 const PAGE_SIZE: NonZeroU16 = NZU16!(77);
525 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(9);
526
527 type TestStore = Db<deterministic::Context, Digest, Vec<u8>, TwoCap>;
529
530 async fn create_test_store(context: deterministic::Context) -> TestStore {
531 let cfg = Config {
532 log: JournalConfig {
533 partition: "journal".into(),
534 write_buffer: NZUsize!(64 * 1024),
535 compression: None,
536 codec_config: ((), ((0..=10000).into(), ())),
537 items_per_section: NZU64!(7),
538 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
539 },
540 translator: TwoCap,
541 init_cache_size: Some(NZUsize!(1024)),
542 };
543 TestStore::init(context, cfg).await.unwrap()
544 }
545
546 async fn apply_entries(
547 db: &mut TestStore,
548 iter: impl IntoIterator<Item = (Digest, Option<Vec<u8>>)> + Send,
549 ) -> Range<Location> {
550 db.apply_batch(iter.into_iter().collect()).await.unwrap()
551 }
552
553 #[test_traced("DEBUG")]
554 pub fn test_store_construct_empty() {
555 let executor = deterministic::Runner::default();
556 executor.start(|mut context| async move {
557 let mut db = create_test_store(context.child("store").with_attribute("index", 0)).await;
558 assert_eq!(db.bounds().end, 1);
559 assert_eq!(db.log.bounds().start, 0);
560 assert_eq!(db.inactivity_floor_loc(), 0);
561 assert!(matches!(db.prune(db.inactivity_floor_loc()).await, Ok(())));
562 assert!(matches!(
563 db.prune(Location::new(1)).await,
564 Err(Error::PruneBeyondMinRequired(_, _))
565 ));
566 assert!(db.get_metadata().await.unwrap().is_none());
567
568 let d1 = Digest::random(&mut context);
570 let v1 = vec![1, 2, 3];
571 apply_entries(&mut db, [(d1, Some(v1))]).await;
572 drop(db);
573
574 let mut db = create_test_store(context.child("store").with_attribute("index", 1)).await;
575 assert_eq!(db.bounds().end, 1);
576
577 let metadata = vec![1, 2, 3];
579 let batch = db.new_batch().finalize(Some(metadata.clone()));
580 let range = db.apply_batch(batch).await.unwrap();
581 assert_eq!(range.start, 1);
582 assert_eq!(range.end, 2);
583 db.commit().await.unwrap();
584 assert_eq!(db.bounds().end, 2);
585 assert!(matches!(db.prune(db.inactivity_floor_loc()).await, Ok(())));
586 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
587
588 let mut db = create_test_store(context.child("store").with_attribute("index", 2)).await;
589 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata));
590
591 apply_entries(
594 &mut db,
595 [(Digest::random(&mut context), Some(vec![1, 2, 3]))],
596 )
597 .await;
598 db.commit().await.unwrap();
599 for _ in 1..100 {
600 db.apply_batch(db.new_batch().finalize(None)).await.unwrap();
601 db.commit().await.unwrap();
602 assert!(db.bounds().end - db.inactivity_floor_loc <= 3);
605 assert!(db.get_metadata().await.unwrap().is_none());
606 }
607
608 db.destroy().await.unwrap();
609 });
610 }
611
612 #[test_traced("DEBUG")]
613 fn test_store_construct_basic() {
614 let executor = deterministic::Runner::default();
615
616 executor.start(|mut ctx| async move {
617 let mut db = create_test_store(ctx.child("store").with_attribute("index", 0)).await;
618
619 assert_eq!(db.bounds().end, 1);
621 assert_eq!(db.inactivity_floor_loc, 0);
622
623 let key = Digest::random(&mut ctx);
624 let value = vec![2, 3, 4, 5];
625
626 let result = db.get(&key).await;
628 assert!(result.unwrap().is_none());
629
630 apply_entries(&mut db, [(key, Some(value.clone()))]).await;
633
634 assert_eq!(*db.bounds().end, 4);
635 assert_eq!(*db.inactivity_floor_loc, 2);
636
637 let fetched_value = db.get(&key).await.unwrap();
639 assert_eq!(fetched_value.unwrap(), value);
640
641 drop(db);
643
644 let mut db = create_test_store(ctx.child("store").with_attribute("index", 1)).await;
646
647 assert_eq!(*db.bounds().end, 1);
649 assert_eq!(*db.inactivity_floor_loc, 0);
650 assert!(db.get_metadata().await.unwrap().is_none());
651
652 let metadata = vec![99, 100];
654 let range = db
655 .apply_batch(
656 db.new_batch()
657 .update(key, value.clone())
658 .finalize(Some(metadata.clone())),
659 )
660 .await
661 .unwrap();
662 assert_eq!(*range.start, 1);
663 assert_eq!(*range.end, 4);
664 db.commit().await.unwrap();
665 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
666
667 assert_eq!(*db.bounds().end, 4);
668 assert_eq!(*db.inactivity_floor_loc, 2);
669
670 let mut db = create_test_store(ctx.child("store").with_attribute("index", 2)).await;
672
673 assert_eq!(*db.bounds().end, 4);
675 assert_eq!(*db.inactivity_floor_loc, 2);
676
677 let fetched_value = db.get(&key).await.unwrap();
679 assert_eq!(fetched_value.unwrap(), value);
680
681 let (k1, v1) = (Digest::random(&mut ctx), vec![2, 3, 4, 5, 6]);
683 let (k2, v2) = (Digest::random(&mut ctx), vec![6, 7, 8]);
684 apply_entries(&mut db, [(k1, Some(v1.clone()))]).await;
685 apply_entries(&mut db, [(k2, Some(v2.clone()))]).await;
686
687 assert_eq!(*db.bounds().end, 10);
688 assert_eq!(*db.inactivity_floor_loc, 5);
689
690 assert_eq!(db.get_metadata().await.unwrap(), None);
693
694 db.commit().await.unwrap();
695 assert_eq!(db.get_metadata().await.unwrap(), None);
696
697 assert_eq!(*db.bounds().end, 10);
699 assert_eq!(*db.inactivity_floor_loc, 5);
700
701 assert_eq!(db.get(&key).await.unwrap().unwrap(), value);
703 assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
704 assert_eq!(db.get(&k2).await.unwrap().unwrap(), v2);
705
706 let mut v1_updated = db.get(&k1).await.unwrap().unwrap();
708 v1_updated.push(7);
709 apply_entries(&mut db, [(k1, Some(v1_updated))]).await;
710 db.commit().await.unwrap();
711 assert_eq!(db.get(&k1).await.unwrap().unwrap(), vec![2, 3, 4, 5, 6, 7]);
712
713 let k3 = Digest::random(&mut ctx);
715 apply_entries(&mut db, [(k3, Some(vec![8]))]).await;
716 db.commit().await.unwrap();
717 assert_eq!(db.get(&k3).await.unwrap().unwrap(), vec![8]);
718
719 db.destroy().await.unwrap();
721 });
722 }
723
724 #[test_traced("DEBUG")]
725 fn test_store_log_replay() {
726 let executor = deterministic::Runner::default();
727
728 executor.start(|mut ctx| async move {
729 let mut db = create_test_store(ctx.child("store").with_attribute("index", 0)).await;
730
731 const UPDATES: u64 = 100;
733 let k = Digest::random(&mut ctx);
734 for _ in 0..UPDATES {
735 let v = vec![1, 2, 3, 4, 5];
736 apply_entries(&mut db, [(k, Some(v.clone()))]).await;
737 }
738
739 let iter = db.snapshot.get(&k);
740 assert_eq!(iter.count(), 1);
741
742 db.commit().await.unwrap();
743 db.sync().await.unwrap();
744 drop(db);
745
746 let mut db = create_test_store(ctx.child("store").with_attribute("index", 1)).await;
748 db.prune(db.inactivity_floor_loc()).await.unwrap();
749
750 let iter = db.snapshot.get(&k);
751 assert_eq!(iter.count(), 1);
752
753 assert_eq!(*db.bounds().end, 400);
756 assert_eq!(*db.inactivity_floor_loc, 398);
758 let floor = db.inactivity_floor_loc;
759
760 assert_eq!(db.log.bounds().start, *floor - *floor % 7);
763
764 db.destroy().await.unwrap();
765 });
766 }
767
768 #[test_traced("DEBUG")]
769 fn test_store_build_snapshot_keys_with_shared_prefix() {
770 let executor = deterministic::Runner::default();
771
772 executor.start(|mut ctx| async move {
773 let mut db = create_test_store(ctx.child("store").with_attribute("index", 0)).await;
774
775 let (k1, v1) = (Digest::random(&mut ctx), vec![1, 2, 3, 4, 5]);
776 let (mut k2, v2) = (Digest::random(&mut ctx), vec![6, 7, 8, 9, 10]);
777
778 k2.0[0..2].copy_from_slice(&k1.0[0..2]);
780
781 apply_entries(&mut db, [(k1, Some(v1.clone()))]).await;
782 apply_entries(&mut db, [(k2, Some(v2.clone()))]).await;
783
784 assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
785 assert_eq!(db.get(&k2).await.unwrap().unwrap(), v2);
786
787 db.commit().await.unwrap();
788 db.sync().await.unwrap();
789 drop(db);
790
791 let db = create_test_store(ctx.child("store").with_attribute("index", 1)).await;
794
795 assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
796 assert_eq!(db.get(&k2).await.unwrap().unwrap(), v2);
797
798 db.destroy().await.unwrap();
799 });
800 }
801
802 #[test_traced("DEBUG")]
803 fn test_store_delete() {
804 let executor = deterministic::Runner::default();
805
806 executor.start(|mut ctx| async move {
807 let mut db = create_test_store(ctx.child("store").with_attribute("index", 0)).await;
808
809 let k = Digest::random(&mut ctx);
811 let v = vec![1, 2, 3, 4, 5];
812 apply_entries(&mut db, [(k, Some(v.clone()))]).await;
813 db.commit().await.unwrap();
814
815 let fetched_value = db.get(&k).await.unwrap();
817 assert_eq!(fetched_value.unwrap(), v);
818
819 assert!(db.get(&k).await.unwrap().is_some());
821 apply_entries(&mut db, [(k, None)]).await;
822
823 let fetched_value = db.get(&k).await.unwrap();
825 assert!(fetched_value.is_none());
826 assert!(db.get(&k).await.unwrap().is_none());
827
828 db.commit().await.unwrap();
830
831 let mut db = create_test_store(ctx.child("store").with_attribute("index", 1)).await;
833 let fetched_value = db.get(&k).await.unwrap();
834 assert!(fetched_value.is_none());
835
836 apply_entries(&mut db, [(k, Some(v.clone()))]).await;
838 let fetched_value = db.get(&k).await.unwrap();
839 assert_eq!(fetched_value.unwrap(), v);
840
841 db.commit().await.unwrap();
843
844 let mut db = create_test_store(ctx.child("store").with_attribute("index", 2)).await;
847 let fetched_value = db.get(&k).await.unwrap();
848 assert_eq!(fetched_value.unwrap(), v);
849
850 let k_n = Digest::random(&mut ctx);
852 let range = apply_entries(&mut db, [(k_n, None)]).await;
853 assert_eq!(range.start, 9);
854 assert_eq!(range.end, 11);
855 db.commit().await.unwrap();
856
857 assert!(db.get(&k_n).await.unwrap().is_none());
858 assert!(db.get(&k).await.unwrap().is_some());
860
861 db.destroy().await.unwrap();
862 });
863 }
864
865 #[test_traced("DEBUG")]
867 fn test_store_pruning() {
868 let executor = deterministic::Runner::default();
869
870 executor.start(|mut ctx| async move {
871 let mut db = create_test_store(ctx.child("store")).await;
872
873 let k_a = Digest::random(&mut ctx);
874 let k_b = Digest::random(&mut ctx);
875
876 let v_a = vec![1];
877 let v_b = vec![];
878 let v_c = vec![4, 5, 6];
879
880 apply_entries(&mut db, [(k_a, Some(v_a.clone()))]).await;
881 apply_entries(&mut db, [(k_b, Some(v_b.clone()))]).await;
882
883 db.commit().await.unwrap();
884 assert_eq!(*db.bounds().end, 7);
885 assert_eq!(*db.inactivity_floor_loc, 3);
886 assert_eq!(db.get(&k_a).await.unwrap().unwrap(), v_a);
887
888 apply_entries(&mut db, [(k_b, Some(v_a.clone()))]).await;
889 apply_entries(&mut db, [(k_a, Some(v_c.clone()))]).await;
890
891 db.commit().await.unwrap();
892 assert_eq!(*db.bounds().end, 15);
893 assert_eq!(*db.inactivity_floor_loc, 12);
894 assert_eq!(db.get(&k_a).await.unwrap().unwrap(), v_c);
895 assert_eq!(db.get(&k_b).await.unwrap().unwrap(), v_a);
896
897 db.destroy().await.unwrap();
898 });
899 }
900
901 #[test_traced("WARN")]
905 pub fn test_store_db_prune_after_unsynced_floor_recovery() {
906 let executor = deterministic::Runner::default();
907 const ELEMENTS: u64 = 1000;
908 executor.start(|context| async move {
909 let mut db = create_test_store(context.child("store").with_attribute("index", 0)).await;
910
911 for i in 0u64..ELEMENTS {
913 let k = Blake3::hash(&i.to_be_bytes());
914 let v = vec![(i % 255) as u8; ((i % 13) + 7) as usize];
915 apply_entries(&mut db, [(k, Some(v))]).await;
916 }
917 db.commit().await.unwrap();
918 let durable_floor = db.inactivity_floor_loc;
919
920 for i in 0u64..ELEMENTS {
923 let k = Blake3::hash(&i.to_be_bytes());
924 let v = vec![((i + 1) % 255) as u8; ((i % 13) + 8) as usize];
925 apply_entries(&mut db, [(k, Some(v))]).await;
926 }
927 let unsynced_floor = db.inactivity_floor_loc;
928 assert!(unsynced_floor > durable_floor);
929
930 db.prune(unsynced_floor).await.unwrap();
932 let op_count = db.bounds().end;
933 drop(db);
934
935 let db = create_test_store(context.child("store").with_attribute("index", 1)).await;
938 assert_eq!(db.bounds().end, op_count);
939 assert_eq!(db.inactivity_floor_loc, unsynced_floor);
940 db.destroy().await.unwrap();
941 });
942 }
943
944 #[test_traced("WARN")]
945 pub fn test_store_db_recovery() {
946 let executor = deterministic::Runner::default();
947 const ELEMENTS: u64 = 1000;
949 executor.start(|context| async move {
950 let db = create_test_store(context.child("store").with_attribute("index", 0)).await;
951
952 {
954 let mut batch = db.new_batch();
955 for i in 0u64..ELEMENTS {
956 let k = Blake3::hash(&i.to_be_bytes());
957 let v = vec![(i % 255) as u8; ((i % 13) + 7) as usize];
958 batch = batch.update(k, v);
959 }
960 }
962 drop(db);
963 let mut db = create_test_store(context.child("store").with_attribute("index", 1)).await;
964 assert_eq!(*db.bounds().end, 1);
965
966 for i in 0u64..ELEMENTS {
968 let k = Blake3::hash(&i.to_be_bytes());
969 let v = vec![(i % 255) as u8; ((i % 13) + 7) as usize];
970 apply_entries(&mut db, [(k, Some(v.clone()))]).await;
971 }
972 db.commit().await.unwrap();
973
974 for i in 0u64..ELEMENTS {
976 if i % 3 != 0 {
977 continue;
978 }
979 let k = Blake3::hash(&i.to_be_bytes());
980 let v = vec![((i + 1) % 255) as u8; ((i % 13) + 8) as usize];
981 apply_entries(&mut db, [(k, Some(v.clone()))]).await;
982 }
983 db.commit().await.unwrap();
984 assert_eq!(db.snapshot.items(), 1000);
985
986 for i in 0u64..ELEMENTS {
988 if i % 7 != 1 {
989 continue;
990 }
991 let k = Blake3::hash(&i.to_be_bytes());
992 apply_entries(&mut db, [(k, None)]).await;
993 }
994 db.commit().await.unwrap();
995 let final_count = db.bounds().end;
996 let final_floor = db.inactivity_floor_loc;
997
998 db.sync().await.unwrap();
1000 drop(db);
1001 let mut db = create_test_store(context.child("store").with_attribute("index", 2)).await;
1002 assert_eq!(db.bounds().end, final_count);
1003 assert_eq!(db.inactivity_floor_loc, final_floor);
1004
1005 db.prune(db.inactivity_floor_loc()).await.unwrap();
1006 assert_eq!(db.log.bounds().start, *final_floor - *final_floor % 7);
1007 assert_eq!(db.snapshot.items(), 857);
1008
1009 db.destroy().await.unwrap();
1010 });
1011 }
1012
1013 #[test_traced("WARN")]
1014 pub fn test_store_commit_after_sync_recovers_without_second_sync() {
1015 let executor = deterministic::Runner::default();
1016 executor.start(|context| async move {
1017 let mut db = create_test_store(context.child("store").with_attribute("index", 0)).await;
1018 let key0 = Blake3::hash(&0u64.to_be_bytes());
1019 let key1 = Blake3::hash(&1u64.to_be_bytes());
1020 let value0 = vec![0, 1, 2];
1021 let value1 = vec![3, 4, 5, 6];
1022
1023 apply_entries(&mut db, [(key0, Some(value0.clone()))]).await;
1025 db.commit().await.unwrap();
1026 db.sync().await.unwrap();
1027
1028 apply_entries(&mut db, [(key1, Some(value1.clone()))]).await;
1030 db.commit().await.unwrap();
1031 let committed_end = db.bounds().end;
1032 let committed_floor = db.inactivity_floor_loc();
1033 drop(db);
1034
1035 let db = create_test_store(context.child("store").with_attribute("index", 1)).await;
1036 assert_eq!(db.bounds().end, committed_end);
1037 assert_eq!(db.inactivity_floor_loc(), committed_floor);
1038 assert_eq!(db.get(&key0).await.unwrap(), Some(value0));
1039 assert_eq!(db.get(&key1).await.unwrap(), Some(value1));
1040
1041 db.destroy().await.unwrap();
1042 });
1043 }
1044
1045 #[test_traced("DEBUG")]
1046 fn test_store_batch() {
1047 let executor = deterministic::Runner::default();
1048
1049 executor.start(|mut ctx| async move {
1050 let mut db = create_test_store(ctx.child("store").with_attribute("index", 0)).await;
1051
1052 assert_eq!(db.bounds().end, 1);
1054 assert_eq!(db.inactivity_floor_loc, 0);
1055
1056 let key = Digest::random(&mut ctx);
1057 let value = vec![2, 3, 4, 5];
1058
1059 let batch = db.new_batch();
1060
1061 let result = batch.get(&key).await;
1063 assert!(result.unwrap().is_none());
1064
1065 let batch = batch.update(key, value.clone());
1067
1068 assert_eq!(db.bounds().end, 1); assert_eq!(db.inactivity_floor_loc, 0);
1070
1071 let fetched_value = batch.get(&key).await.unwrap();
1073 assert_eq!(fetched_value.unwrap(), value);
1074 db.apply_batch(batch.finalize(None)).await.unwrap();
1075 drop(db);
1076
1077 let mut db = create_test_store(ctx.child("store").with_attribute("index", 1)).await;
1079
1080 assert_eq!(db.bounds().end, 1);
1082 assert_eq!(db.inactivity_floor_loc, 0);
1083 assert!(db.get_metadata().await.unwrap().is_none());
1084
1085 let metadata = vec![99, 100];
1087 let range = db
1088 .apply_batch(
1089 db.new_batch()
1090 .update(key, value.clone())
1091 .finalize(Some(metadata.clone())),
1092 )
1093 .await
1094 .unwrap();
1095 assert_eq!(range.start, 1);
1096 assert_eq!(range.end, 4);
1097 db.commit().await.unwrap();
1098 assert_eq!(db.get_metadata().await.unwrap(), Some(metadata.clone()));
1099 drop(db);
1100
1101 let db = create_test_store(ctx.child("store").with_attribute("index", 2)).await;
1103
1104 assert_eq!(db.bounds().end, 4);
1106 assert_eq!(db.inactivity_floor_loc, 2);
1107
1108 let fetched_value = db.get(&key).await.unwrap();
1110 assert_eq!(fetched_value.unwrap(), value);
1111
1112 db.destroy().await.unwrap();
1114 });
1115 }
1116
1117 fn is_send<T: Send>(_: T) {}
1118
1119 #[allow(dead_code)]
1120 fn assert_read_futures_are_send(db: &mut TestStore, key: Digest, loc: Location) {
1121 is_send(db.get(&key));
1122 is_send(db.get_metadata());
1123 is_send(db.prune(loc));
1124 is_send(db.sync());
1125 }
1126
1127 #[allow(dead_code)]
1128 fn assert_write_futures_are_send(
1129 db: &mut Db<deterministic::Context, Digest, Vec<u8>, TwoCap>,
1130 key: Digest,
1131 value: Vec<u8>,
1132 ) {
1133 is_send(db.get(&key));
1134 is_send(db.apply_batch(Changeset::from([(key, Some(value))])));
1135 is_send(db.apply_batch(Changeset::from([(key, None)])));
1136 let batch = db.new_batch();
1137 is_send(batch.get(&key));
1138 }
1139
1140 #[allow(dead_code)]
1141 fn assert_commit_is_send(db: &mut Db<deterministic::Context, Digest, Vec<u8>, TwoCap>) {
1142 is_send(db.commit());
1143 }
1144}