1use commonware_codec::Codec;
13use commonware_runtime::Handle;
14use commonware_utils::Array;
15use std::future::Future;
16use thiserror::Error;
17
18pub mod immutable;
19pub mod prunable;
20
21#[cfg(all(test, feature = "arbitrary"))]
22mod conformance;
23
24pub enum Identifier<'a, K: Array> {
26 Index(u64),
27 Key(&'a K),
28}
29
30#[derive(Debug, Error)]
32pub enum Error {
33 #[error("journal error: {0}")]
34 Journal(#[from] crate::journal::Error),
35 #[error("ordinal error: {0}")]
36 Ordinal(#[from] crate::ordinal::Error),
37 #[error("metadata error: {0}")]
38 Metadata(#[from] crate::metadata::Error),
39 #[error("freezer error: {0}")]
40 Freezer(#[from] crate::freezer::Error),
41 #[error("record corrupted")]
42 RecordCorrupted,
43 #[error("already pruned to: {0}")]
44 AlreadyPrunedTo(u64),
45 #[error("record too large")]
46 RecordTooLarge,
47}
48
49pub trait Archive: Send {
51 type Key: Array;
53
54 type Value: Codec + Send;
56
57 fn put(
64 &mut self,
65 index: u64,
66 key: Self::Key,
67 value: Self::Value,
68 ) -> impl Future<Output = Result<(), Error>> + Send;
69
70 fn put_sync(
72 &mut self,
73 index: u64,
74 key: Self::Key,
75 value: Self::Value,
76 ) -> impl Future<Output = Result<(), Error>> + Send {
77 async move {
78 self.put(index, key, value).await?;
79 self.sync().await
80 }
81 }
82
83 fn put_start_sync(
89 &mut self,
90 index: u64,
91 key: Self::Key,
92 value: Self::Value,
93 ) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
94 async move {
95 self.put(index, key, value).await?;
96 self.start_sync().await
97 }
98 }
99
100 fn get<'a>(
106 &'a self,
107 identifier: Identifier<'a, Self::Key>,
108 ) -> impl Future<Output = Result<Option<Self::Value>, Error>> + Send + use<'a, Self>;
109
110 fn has<'a>(
112 &'a self,
113 identifier: Identifier<'a, Self::Key>,
114 ) -> impl Future<Output = Result<bool, Error>> + Send + use<'a, Self>;
115
116 fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>);
121
122 fn missing_items(&self, index: u64, max: usize) -> Vec<u64>;
127
128 fn ranges(&self) -> impl Iterator<Item = (u64, u64)>;
130
131 fn ranges_from(&self, from: u64) -> impl Iterator<Item = (u64, u64)>;
133
134 fn first_index(&self) -> Option<u64>;
136
137 fn last_index(&self) -> Option<u64>;
139
140 fn sync(&mut self) -> impl Future<Output = Result<(), Error>> + Send;
142
143 fn start_sync(&mut self) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
149 async move {
150 self.sync().await?;
151 Ok(Handle::ready(Ok(())))
152 }
153 }
154
155 fn destroy(self) -> impl Future<Output = Result<(), Error>> + Send;
157}
158
159pub trait MultiArchive: Archive {
165 fn get_all(
169 &self,
170 index: u64,
171 ) -> impl Future<Output = Result<Option<Vec<Self::Value>>, Error>> + Send + use<'_, Self>;
172
173 fn has_at<'a>(
178 &'a self,
179 index: u64,
180 key: &'a Self::Key,
181 ) -> impl Future<Output = Result<bool, Error>> + Send + use<'a, Self>;
182
183 fn put_multi(
189 &mut self,
190 index: u64,
191 key: Self::Key,
192 value: Self::Value,
193 ) -> impl Future<Output = Result<(), Error>> + Send;
194
195 fn put_multi_sync(
197 &mut self,
198 index: u64,
199 key: Self::Key,
200 value: Self::Value,
201 ) -> impl Future<Output = Result<(), Error>> + Send {
202 async move {
203 self.put_multi(index, key, value).await?;
204 self.sync().await
205 }
206 }
207
208 fn put_multi_start_sync(
210 &mut self,
211 index: u64,
212 key: Self::Key,
213 value: Self::Value,
214 ) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
215 async move {
216 self.put_multi(index, key, value).await?;
217 self.start_sync().await
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::translator::TwoCap;
226 use commonware_codec::DecodeExt;
227 use commonware_macros::{test_group, test_traced};
228 use commonware_runtime::{
229 buffer::paged::CacheRef,
230 deterministic::{self, Context},
231 telemetry::metrics::has_metric_value,
232 Metrics as _, Runner, Supervisor as _,
233 };
234 use commonware_utils::{sequence::FixedBytes, NZUsize, NZU16, NZU64};
235 use rand::RngExt as _;
236 use std::{
237 collections::BTreeMap,
238 num::{NonZeroU16, NonZeroUsize},
239 };
240
241 fn test_key(key: &str) -> FixedBytes<64> {
242 let mut buf = [0u8; 64];
243 let key = key.as_bytes();
244 assert!(key.len() <= buf.len());
245 buf[..key.len()].copy_from_slice(key);
246 FixedBytes::decode(buf.as_ref()).unwrap()
247 }
248
249 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
250 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
251
252 async fn create_prunable(
253 context: Context,
254 compression: Option<u8>,
255 ) -> impl MultiArchive<Key = FixedBytes<64>, Value = i32> {
256 let cfg = prunable::Config {
257 translator: TwoCap,
258 key_partition: "test-key".into(),
259 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
260 value_partition: "test-value".into(),
261 compression,
262 codec_config: (),
263 items_per_section: NZU64!(1024),
264 key_write_buffer: NZUsize!(1024),
265 value_write_buffer: NZUsize!(1024),
266 replay_buffer: NZUsize!(1024),
267 };
268 prunable::Archive::init(context, cfg).await.unwrap()
269 }
270
271 async fn create_immutable(
272 context: Context,
273 compression: Option<u8>,
274 ) -> impl Archive<Key = FixedBytes<64>, Value = i32> {
275 let cfg = immutable::Config {
276 metadata_partition: "test-metadata".into(),
277 freezer_table_partition: "test-freezer-table".into(),
278 freezer_table_initial_size: 64,
279 freezer_table_resize_frequency: 2,
280 freezer_table_resize_chunk_size: 32,
281 freezer_key_partition: "test-freezer-key".into(),
282 freezer_key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
283 freezer_value_partition: "test-freezer-value".into(),
284 freezer_value_target_size: 1024 * 1024,
285 freezer_value_compression: compression,
286 ordinal_partition: "test-ordinal".into(),
287 items_per_section: NZU64!(1024),
288 freezer_key_write_buffer: NZUsize!(1024 * 1024),
289 freezer_value_write_buffer: NZUsize!(1024 * 1024),
290 ordinal_write_buffer: NZUsize!(1024 * 1024),
291 replay_buffer: NZUsize!(1024 * 1024),
292 codec_config: (),
293 };
294 immutable::Archive::init(context, cfg).await.unwrap()
295 }
296
297 async fn test_put_get_impl(mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
298 let index = 1u64;
299 let key = test_key("testkey");
300 let data = 1;
301
302 let has = archive
304 .has(Identifier::Index(index))
305 .await
306 .expect("Failed to check key");
307 assert!(!has);
308 let has = archive
309 .has(Identifier::Key(&key))
310 .await
311 .expect("Failed to check key");
312 assert!(!has);
313
314 archive
316 .put(index, key.clone(), data)
317 .await
318 .expect("Failed to put data");
319
320 let has = archive
322 .has(Identifier::Index(index))
323 .await
324 .expect("Failed to check key");
325 assert!(has);
326 let has = archive
327 .has(Identifier::Key(&key))
328 .await
329 .expect("Failed to check key");
330 assert!(has);
331
332 let retrieved = archive
334 .get(Identifier::Key(&key))
335 .await
336 .expect("Failed to get data");
337 assert_eq!(retrieved, Some(data));
338
339 let retrieved = archive
341 .get(Identifier::Index(index))
342 .await
343 .expect("Failed to get data");
344 assert_eq!(retrieved, Some(data));
345
346 archive.sync().await.expect("Failed to sync data");
348 }
349
350 #[test_traced]
351 fn test_put_get_prunable_no_compression() {
352 let executor = deterministic::Runner::default();
353 executor.start(|context| async move {
354 let archive = create_prunable(context, None).await;
355 test_put_get_impl(archive).await;
356 });
357 }
358
359 #[test_traced]
360 fn test_put_get_prunable_compression() {
361 let executor = deterministic::Runner::default();
362 executor.start(|context| async move {
363 let archive = create_prunable(context, Some(3)).await;
364 test_put_get_impl(archive).await;
365 });
366 }
367
368 #[test_traced]
369 fn test_put_get_immutable_no_compression() {
370 let executor = deterministic::Runner::default();
371 executor.start(|context| async move {
372 let archive = create_immutable(context, None).await;
373 test_put_get_impl(archive).await;
374 });
375 }
376
377 #[test_traced]
378 fn test_put_get_immutable_compression() {
379 let executor = deterministic::Runner::default();
380 executor.start(|context| async move {
381 let archive = create_immutable(context, Some(3)).await;
382 test_put_get_impl(archive).await;
383 });
384 }
385
386 async fn test_duplicate_key_impl(mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
387 let index = 1u64;
388 let key = test_key("duplicate");
389 let data1 = 1;
390 let data2 = 2;
391
392 archive
394 .put(index, key.clone(), data1)
395 .await
396 .expect("Failed to put data");
397
398 archive
400 .put(index, key.clone(), data2)
401 .await
402 .expect("Duplicate put should not fail");
403
404 let retrieved = archive
406 .get(Identifier::Index(index))
407 .await
408 .expect("Failed to get data")
409 .expect("Data not found");
410 assert_eq!(retrieved, data1);
411
412 let retrieved = archive
413 .get(Identifier::Key(&key))
414 .await
415 .expect("Failed to get data")
416 .expect("Data not found");
417 assert_eq!(retrieved, data1);
418 }
419
420 #[test_traced]
421 fn test_duplicate_key_prunable_no_compression() {
422 let executor = deterministic::Runner::default();
423 executor.start(|context| async move {
424 let archive = create_prunable(context, None).await;
425 test_duplicate_key_impl(archive).await;
426 });
427 }
428
429 #[test_traced]
430 fn test_duplicate_key_prunable_compression() {
431 let executor = deterministic::Runner::default();
432 executor.start(|context| async move {
433 let archive = create_prunable(context, Some(3)).await;
434 test_duplicate_key_impl(archive).await;
435 });
436 }
437
438 #[test_traced]
439 fn test_duplicate_key_immutable_no_compression() {
440 let executor = deterministic::Runner::default();
441 executor.start(|context| async move {
442 let archive = create_immutable(context, None).await;
443 test_duplicate_key_impl(archive).await;
444 });
445 }
446
447 async fn test_duplicate_key_cross_index_impl(
448 mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>,
449 ) {
450 let key = test_key("dupe-xindex");
453 archive.put(2, key.clone(), 20).await.expect("put(2)");
454 archive.put(5, key.clone(), 50).await.expect("put(5)");
455
456 assert_eq!(
458 archive.get(Identifier::Index(2)).await.unwrap(),
459 Some(20),
460 "Index(2) must resolve to the value stored at 2"
461 );
462 assert_eq!(
463 archive.get(Identifier::Index(5)).await.unwrap(),
464 Some(50),
465 "Index(5) must resolve to the value stored at 5"
466 );
467
468 let got = archive
471 .get(Identifier::Key(&key))
472 .await
473 .unwrap()
474 .expect("key lookup must find at least one entry");
475 assert!(got == 20 || got == 50, "unexpected value: {got}");
476 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
477 }
478
479 #[test_traced]
480 fn test_duplicate_key_cross_index_prunable_no_compression() {
481 let executor = deterministic::Runner::default();
482 executor.start(|context| async move {
483 let archive = create_prunable(context, None).await;
484 test_duplicate_key_cross_index_impl(archive).await;
485 });
486 }
487
488 #[test_traced]
489 fn test_duplicate_key_cross_index_prunable_compression() {
490 let executor = deterministic::Runner::default();
491 executor.start(|context| async move {
492 let archive = create_prunable(context, Some(3)).await;
493 test_duplicate_key_cross_index_impl(archive).await;
494 });
495 }
496
497 #[test_traced]
498 fn test_duplicate_key_cross_index_immutable_no_compression() {
499 let executor = deterministic::Runner::default();
500 executor.start(|context| async move {
501 let archive = create_immutable(context, None).await;
502 test_duplicate_key_cross_index_impl(archive).await;
503 });
504 }
505
506 #[test_traced]
507 fn test_duplicate_key_cross_index_immutable_compression() {
508 let executor = deterministic::Runner::default();
509 executor.start(|context| async move {
510 let archive = create_immutable(context, Some(3)).await;
511 test_duplicate_key_cross_index_impl(archive).await;
512 });
513 }
514
515 #[test_traced]
516 fn test_duplicate_key_immutable_compression() {
517 let executor = deterministic::Runner::default();
518 executor.start(|context| async move {
519 let archive = create_immutable(context, Some(3)).await;
520 test_duplicate_key_impl(archive).await;
521 });
522 }
523
524 async fn test_get_nonexistent_impl(archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
525 let index = 1u64;
527 let retrieved: Option<i32> = archive
528 .get(Identifier::Index(index))
529 .await
530 .expect("Failed to get data");
531 assert!(retrieved.is_none());
532
533 let key = test_key("nonexistent");
535 let retrieved = archive
536 .get(Identifier::Key(&key))
537 .await
538 .expect("Failed to get data");
539 assert!(retrieved.is_none());
540 }
541
542 #[test_traced]
543 fn test_get_nonexistent_prunable_no_compression() {
544 let executor = deterministic::Runner::default();
545 executor.start(|context| async move {
546 let archive = create_prunable(context, None).await;
547 test_get_nonexistent_impl(archive).await;
548 });
549 }
550
551 #[test_traced]
552 fn test_get_nonexistent_prunable_compression() {
553 let executor = deterministic::Runner::default();
554 executor.start(|context| async move {
555 let archive = create_prunable(context, Some(3)).await;
556 test_get_nonexistent_impl(archive).await;
557 });
558 }
559
560 #[test_traced]
561 fn test_get_nonexistent_immutable_no_compression() {
562 let executor = deterministic::Runner::default();
563 executor.start(|context| async move {
564 let archive = create_immutable(context, None).await;
565 test_get_nonexistent_impl(archive).await;
566 });
567 }
568
569 #[test_traced]
570 fn test_get_nonexistent_immutable_compression() {
571 let executor = deterministic::Runner::default();
572 executor.start(|context| async move {
573 let archive = create_immutable(context, Some(3)).await;
574 test_get_nonexistent_impl(archive).await;
575 });
576 }
577
578 async fn test_persistence_impl<A, F, Fut>(context: Context, creator: F, compression: Option<u8>)
579 where
580 A: Archive<Key = FixedBytes<64>, Value = i32>,
581 F: Fn(Context, Option<u8>) -> Fut,
582 Fut: Future<Output = A>,
583 {
584 {
586 let mut archive = creator(context.child("first"), compression).await;
587
588 let keys = vec![
590 (1u64, test_key("key1"), 1),
591 (2u64, test_key("key2"), 2),
592 (3u64, test_key("key3"), 3),
593 ];
594
595 for (index, key, data) in &keys {
596 archive
597 .put(*index, key.clone(), *data)
598 .await
599 .expect("Failed to put data");
600 }
601
602 archive.sync().await.expect("Failed to sync archive");
604 }
605
606 {
608 let archive = creator(context.child("second"), compression).await;
609
610 let keys = vec![
612 (1u64, test_key("key1"), 1),
613 (2u64, test_key("key2"), 2),
614 (3u64, test_key("key3"), 3),
615 ];
616
617 for (index, key, expected_data) in &keys {
618 let retrieved = archive
619 .get(Identifier::Index(*index))
620 .await
621 .expect("Failed to get data")
622 .expect("Data not found");
623 assert_eq!(retrieved, *expected_data);
624
625 let retrieved = archive
626 .get(Identifier::Key(key))
627 .await
628 .expect("Failed to get data")
629 .expect("Data not found");
630 assert_eq!(retrieved, *expected_data);
631 }
632 }
633 }
634
635 #[test_traced]
636 fn test_persistence_prunable_no_compression() {
637 let executor = deterministic::Runner::default();
638 executor.start(|context| async move {
639 test_persistence_impl(context, create_prunable, None).await;
640 });
641 }
642
643 #[test_traced]
644 fn test_persistence_prunable_compression() {
645 let executor = deterministic::Runner::default();
646 executor.start(|context| async move {
647 test_persistence_impl(context, create_prunable, Some(3)).await;
648 });
649 }
650
651 #[test_traced]
652 fn test_persistence_immutable_no_compression() {
653 let executor = deterministic::Runner::default();
654 executor.start(|context| async move {
655 test_persistence_impl(context, create_immutable, None).await;
656 });
657 }
658
659 #[test_traced]
660 fn test_persistence_immutable_compression() {
661 let executor = deterministic::Runner::default();
662 executor.start(|context| async move {
663 test_persistence_impl(context, create_immutable, Some(3)).await;
664 });
665 }
666
667 async fn test_ranges_impl<A, F, Fut>(mut context: Context, creator: F, compression: Option<u8>)
668 where
669 A: Archive<Key = FixedBytes<64>, Value = i32>,
670 F: Fn(Context, Option<u8>) -> Fut,
671 Fut: Future<Output = A>,
672 {
673 let mut keys = BTreeMap::new();
674 {
675 let mut archive = creator(context.child("first"), compression).await;
676
677 let mut last_index = 0u64;
679 while keys.len() < 100 {
680 let gap: u64 = context.random_range(1..=10);
681 let index = last_index + gap;
682 last_index = index;
683
684 let mut key_bytes = [0u8; 64];
685 context.fill(&mut key_bytes);
686 let key = FixedBytes::<64>::decode(key_bytes.as_ref()).unwrap();
687 let data: i32 = context.random();
688
689 if keys.contains_key(&index) {
690 continue;
691 }
692 keys.insert(index, (key.clone(), data));
693
694 archive
695 .put(index, key, data)
696 .await
697 .expect("Failed to put data");
698 }
699
700 archive.sync().await.expect("Failed to sync archive");
701 }
702
703 {
704 let archive = creator(context.child("second"), compression).await;
705 let sorted_indices: Vec<u64> = keys.keys().cloned().collect();
706
707 let (current_end, start_next) = archive.next_gap(0);
709 assert!(current_end.is_none());
710 assert_eq!(start_next, Some(sorted_indices[0]));
711
712 let mut i = 0;
714 while i < sorted_indices.len() {
715 let current_index = sorted_indices[i];
716
717 let mut j = i;
719 while j + 1 < sorted_indices.len() && sorted_indices[j + 1] == sorted_indices[j] + 1
720 {
721 j += 1;
722 }
723 let block_end_index = sorted_indices[j];
724 let next_actual_index = if j + 1 < sorted_indices.len() {
725 Some(sorted_indices[j + 1])
726 } else {
727 None
728 };
729
730 let (current_end, start_next) = archive.next_gap(current_index);
731 assert_eq!(current_end, Some(block_end_index));
732 assert_eq!(start_next, next_actual_index);
733
734 if let Some(next_index) = next_actual_index {
736 if next_index > block_end_index + 1 {
737 let in_gap_index = block_end_index + 1;
738 let (current_end, start_next) = archive.next_gap(in_gap_index);
739 assert!(current_end.is_none());
740 assert_eq!(start_next, Some(next_index));
741 }
742 }
743 i = j + 1;
744 }
745
746 let last_index = *sorted_indices.last().unwrap();
748 let (current_end, start_next) = archive.next_gap(last_index);
749 assert!(current_end.is_some());
750 assert!(start_next.is_none());
751 }
752 }
753
754 #[test_traced]
755 fn test_ranges_prunable_no_compression() {
756 let executor = deterministic::Runner::default();
757 executor.start(|context| async move {
758 test_ranges_impl(context, create_prunable, None).await;
759 });
760 }
761
762 #[test_traced]
763 fn test_ranges_prunable_compression() {
764 let executor = deterministic::Runner::default();
765 executor.start(|context| async move {
766 test_ranges_impl(context, create_prunable, Some(3)).await;
767 });
768 }
769
770 #[test_traced]
771 fn test_ranges_immutable_no_compression() {
772 let executor = deterministic::Runner::default();
773 executor.start(|context| async move {
774 test_ranges_impl(context, create_immutable, None).await;
775 });
776 }
777
778 #[test_traced]
779 fn test_ranges_immutable_compression() {
780 let executor = deterministic::Runner::default();
781 executor.start(|context| async move {
782 test_ranges_impl(context, create_immutable, Some(3)).await;
783 });
784 }
785
786 async fn test_many_keys_impl<A, F, Fut>(
787 mut context: Context,
788 creator: F,
789 compression: Option<u8>,
790 num: usize,
791 ) where
792 A: Archive<Key = FixedBytes<64>, Value = i32>,
793 F: Fn(Context, Option<u8>) -> Fut,
794 Fut: Future<Output = A>,
795 {
796 let mut keys = BTreeMap::new();
798 {
799 let mut archive = creator(context.child("first"), compression).await;
800 while keys.len() < num {
801 let index = keys.len() as u64;
802 let mut key = [0u8; 64];
803 context.fill(&mut key);
804 let key = FixedBytes::<64>::decode(key.as_ref()).unwrap();
805 let data: i32 = context.random();
806
807 archive
808 .put(index, key.clone(), data)
809 .await
810 .expect("Failed to put data");
811 keys.insert(key, (index, data));
812
813 if context.random_bool(0.1) {
815 archive.sync().await.expect("Failed to sync archive");
816 }
817 }
818 archive.sync().await.expect("Failed to sync archive");
819
820 for (key, (index, data)) in &keys {
822 let retrieved = archive
823 .get(Identifier::Index(*index))
824 .await
825 .expect("Failed to get data")
826 .expect("Data not found");
827 assert_eq!(&retrieved, data);
828 let retrieved = archive
829 .get(Identifier::Key(key))
830 .await
831 .expect("Failed to get data")
832 .expect("Data not found");
833 assert_eq!(&retrieved, data);
834 }
835 }
836
837 {
839 let archive = creator(context.child("second"), compression).await;
840
841 for (key, (index, data)) in &keys {
843 let retrieved = archive
844 .get(Identifier::Index(*index))
845 .await
846 .expect("Failed to get data")
847 .expect("Data not found");
848 assert_eq!(&retrieved, data);
849 let retrieved = archive
850 .get(Identifier::Key(key))
851 .await
852 .expect("Failed to get data")
853 .expect("Data not found");
854 assert_eq!(&retrieved, data);
855 }
856 }
857 }
858
859 fn test_many_keys_determinism<F, Fut, A>(creator: F, compression: Option<u8>, num: usize)
860 where
861 A: Archive<Key = FixedBytes<64>, Value = i32>,
862 F: Fn(Context, Option<u8>) -> Fut + Copy + Send + 'static,
863 Fut: Future<Output = A> + Send,
864 {
865 let executor = deterministic::Runner::default();
866 let state1 = executor.start(|context| async move {
867 test_many_keys_impl(context.child("storage"), creator, compression, num).await;
868 context.auditor().state()
869 });
870 let executor = deterministic::Runner::default();
871 let state2 = executor.start(|context| async move {
872 test_many_keys_impl(context.child("storage"), creator, compression, num).await;
873 context.auditor().state()
874 });
875 assert_eq!(state1, state2);
876 }
877
878 #[test_traced]
879 fn test_many_keys_prunable_no_compression() {
880 test_many_keys_determinism(create_prunable, None, 1_000);
881 }
882
883 #[test_traced]
884 fn test_many_keys_prunable_compression() {
885 test_many_keys_determinism(create_prunable, Some(3), 1_000);
886 }
887
888 #[test_traced]
889 fn test_many_keys_immutable_no_compression() {
890 test_many_keys_determinism(create_immutable, None, 1_000);
891 }
892
893 #[test_traced]
894 fn test_many_keys_immutable_compression() {
895 test_many_keys_determinism(create_immutable, Some(3), 1_000);
896 }
897
898 #[test_group("slow")]
899 #[test_traced]
900 fn test_many_keys_prunable_large() {
901 test_many_keys_determinism(create_prunable, None, 50_000);
902 }
903
904 #[test_group("slow")]
905 #[test_traced]
906 fn test_many_keys_immutable_large() {
907 test_many_keys_determinism(create_immutable, None, 50_000);
908 }
909
910 async fn test_put_multi_and_get_impl(
911 context: Context,
912 mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
913 ) {
914 let index = 5u64;
916 let key_a = test_key("aaa");
917 let key_b = test_key("bbb");
918 let key_c = test_key("ccc");
919
920 archive
921 .put_multi(index, key_a.clone(), 10)
922 .await
923 .expect("put_multi a");
924 archive
925 .put_multi(index, key_b.clone(), 20)
926 .await
927 .expect("put_multi b");
928 archive
929 .put_multi(index, key_c.clone(), 30)
930 .await
931 .expect("put_multi c");
932
933 assert_eq!(
935 archive.get(Identifier::Key(&key_a)).await.unwrap(),
936 Some(10)
937 );
938 assert_eq!(
939 archive.get(Identifier::Key(&key_b)).await.unwrap(),
940 Some(20)
941 );
942 assert_eq!(
943 archive.get(Identifier::Key(&key_c)).await.unwrap(),
944 Some(30)
945 );
946
947 let missing = test_key("zzz");
949 assert_eq!(archive.get(Identifier::Key(&missing)).await.unwrap(), None);
950
951 let buffer = context.encode();
953 assert!(has_metric_value(&buffer, "items_tracked", 1));
954 }
955
956 #[test_traced]
957 fn test_put_multi_and_get_prunable() {
958 let executor = deterministic::Runner::default();
959 executor.start(|context| async move {
960 let archive = create_prunable(context.child("storage"), None).await;
961 test_put_multi_and_get_impl(context, archive).await;
962 });
963 }
964
965 async fn test_put_multi_duplicate_key_impl(
966 context: Context,
967 mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
968 ) {
969 let key = test_key("dup");
970 archive.put_multi(5, key.clone(), 10).await.unwrap();
971 archive.put_multi(7, key.clone(), 20).await.unwrap();
972
973 assert_eq!(archive.get(Identifier::Index(5)).await.unwrap(), Some(10));
975 assert_eq!(archive.get(Identifier::Index(7)).await.unwrap(), Some(20));
976 assert_eq!(archive.get_all(5).await.unwrap(), Some(vec![10]));
977 assert_eq!(archive.get_all(7).await.unwrap(), Some(vec![20]));
978
979 assert!(matches!(
981 archive.get(Identifier::Key(&key)).await.unwrap(),
982 Some(10 | 20)
983 ));
984
985 let buffer = context.encode();
986 assert!(has_metric_value(&buffer, "items_tracked", 2));
987 }
988
989 #[test_traced]
990 fn test_put_multi_duplicate_key_prunable() {
991 let executor = deterministic::Runner::default();
992 executor.start(|context| async move {
993 let archive = create_prunable(context.child("storage"), None).await;
994 test_put_multi_duplicate_key_impl(context, archive).await;
995 });
996 }
997
998 async fn test_get_all_impl(mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>) {
999 archive.put_multi(5, test_key("aaa"), 10).await.unwrap();
1001 archive.put_multi(5, test_key("bbb"), 20).await.unwrap();
1002 archive.put_multi(5, test_key("ccc"), 30).await.unwrap();
1003
1004 archive.put_multi(7, test_key("ddd"), 40).await.unwrap();
1006
1007 let all = archive.get_all(5).await.unwrap();
1009 assert_eq!(all, Some(vec![10, 20, 30]));
1010
1011 let all = archive.get_all(7).await.unwrap();
1013 assert_eq!(all, Some(vec![40]));
1014
1015 let all = archive.get_all(99).await.unwrap();
1017 assert_eq!(all, None);
1018
1019 assert_eq!(archive.get(Identifier::Index(5)).await.unwrap(), Some(10));
1021 }
1022
1023 #[test_traced]
1024 fn test_get_all_prunable() {
1025 let executor = deterministic::Runner::default();
1026 executor.start(|context| async move {
1027 let archive = create_prunable(context, None).await;
1028 test_get_all_impl(archive).await;
1029 });
1030 }
1031
1032 async fn test_put_multi_preserves_archive_put_semantics_impl(
1033 mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
1034 ) {
1035 archive
1037 .put_multi(1, test_key("aaa"), 10)
1038 .await
1039 .expect("put_multi");
1040 archive
1041 .put_multi(1, test_key("bbb"), 20)
1042 .await
1043 .expect("put_multi");
1044
1045 archive
1047 .put(1, test_key("ccc"), 30)
1048 .await
1049 .expect("Archive::put should no-op");
1050
1051 assert_eq!(
1053 archive
1054 .get(Identifier::Key(&test_key("aaa")))
1055 .await
1056 .unwrap(),
1057 Some(10)
1058 );
1059 assert_eq!(
1060 archive
1061 .get(Identifier::Key(&test_key("bbb")))
1062 .await
1063 .unwrap(),
1064 Some(20)
1065 );
1066 assert_eq!(
1067 archive
1068 .get(Identifier::Key(&test_key("ccc")))
1069 .await
1070 .unwrap(),
1071 None
1072 );
1073
1074 let first = archive
1076 .get(Identifier::Index(1))
1077 .await
1078 .unwrap()
1079 .expect("should find first");
1080 assert_eq!(first, 10);
1081 }
1082
1083 #[test_traced]
1084 fn test_put_multi_preserves_archive_put_semantics_prunable() {
1085 let executor = deterministic::Runner::default();
1086 executor.start(|context| async move {
1087 let archive = create_prunable(context, None).await;
1088 test_put_multi_preserves_archive_put_semantics_impl(archive).await;
1089 });
1090 }
1091
1092 async fn test_put_multi_restart_impl<A, F, Fut>(
1093 context: Context,
1094 creator: F,
1095 compression: Option<u8>,
1096 ) where
1097 A: MultiArchive<Key = FixedBytes<64>, Value = i32>,
1098 F: Fn(Context, Option<u8>) -> Fut,
1099 Fut: Future<Output = A>,
1100 {
1101 {
1103 let mut archive = creator(
1104 context.child("init").with_attribute("index", 1),
1105 compression,
1106 )
1107 .await;
1108 archive.put_multi(5, test_key("aaa"), 10).await.unwrap();
1109 archive.put_multi(5, test_key("bbb"), 20).await.unwrap();
1110 archive.put_multi(7, test_key("ccc"), 30).await.unwrap();
1111 archive.sync().await.unwrap();
1112 }
1113
1114 let archive = creator(
1116 context.child("init").with_attribute("index", 2),
1117 compression,
1118 )
1119 .await;
1120
1121 assert_eq!(
1122 archive
1123 .get(Identifier::Key(&test_key("aaa")))
1124 .await
1125 .unwrap(),
1126 Some(10)
1127 );
1128 assert_eq!(
1129 archive
1130 .get(Identifier::Key(&test_key("bbb")))
1131 .await
1132 .unwrap(),
1133 Some(20)
1134 );
1135 assert_eq!(
1136 archive
1137 .get(Identifier::Key(&test_key("ccc")))
1138 .await
1139 .unwrap(),
1140 Some(30)
1141 );
1142
1143 let buffer = context.encode();
1145 assert!(has_metric_value(&buffer, "items_tracked", 2));
1146 }
1147
1148 #[test_traced]
1149 fn test_put_multi_restart_prunable() {
1150 let executor = deterministic::Runner::default();
1151 executor.start(|context| async move {
1152 test_put_multi_restart_impl(context, create_prunable, None).await;
1153 });
1154 }
1155
1156 async fn test_put_multi_mixed_indices_impl(
1157 context: Context,
1158 mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
1159 ) {
1160 archive.put(1, test_key("single"), 100).await.unwrap();
1162 archive
1163 .put_multi(2, test_key("multi-a"), 200)
1164 .await
1165 .unwrap();
1166 archive
1167 .put_multi(2, test_key("multi-b"), 201)
1168 .await
1169 .unwrap();
1170 archive
1171 .put_multi(3, test_key("multi-c"), 300)
1172 .await
1173 .unwrap();
1174
1175 assert_eq!(
1177 archive
1178 .get(Identifier::Key(&test_key("single")))
1179 .await
1180 .unwrap(),
1181 Some(100)
1182 );
1183 assert_eq!(
1184 archive
1185 .get(Identifier::Key(&test_key("multi-a")))
1186 .await
1187 .unwrap(),
1188 Some(200)
1189 );
1190 assert_eq!(
1191 archive
1192 .get(Identifier::Key(&test_key("multi-b")))
1193 .await
1194 .unwrap(),
1195 Some(201)
1196 );
1197 assert_eq!(
1198 archive
1199 .get(Identifier::Key(&test_key("multi-c")))
1200 .await
1201 .unwrap(),
1202 Some(300)
1203 );
1204
1205 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(200));
1207
1208 let (end, next) = archive.next_gap(1);
1210 assert_eq!(end, Some(3));
1211 assert!(next.is_none());
1212
1213 let buffer = context.encode();
1214 assert!(has_metric_value(&buffer, "items_tracked", 3));
1215 }
1216
1217 #[test_traced]
1218 fn test_put_multi_mixed_indices_prunable() {
1219 let executor = deterministic::Runner::default();
1220 executor.start(|context| async move {
1221 let archive = create_prunable(context.child("storage"), None).await;
1222 test_put_multi_mixed_indices_impl(context, archive).await;
1223 });
1224 }
1225
1226 fn assert_send<T: Send>(_: T) {}
1227
1228 #[allow(dead_code)]
1229 fn assert_archive_futures_are_send<T: super::Archive>(
1230 archive: &mut T,
1231 key: T::Key,
1232 value: T::Value,
1233 ) where
1234 T::Key: Clone,
1235 T::Value: Clone,
1236 {
1237 assert_send(archive.put(1, key.clone(), value.clone()));
1238 assert_send(archive.put_sync(2, key.clone(), value.clone()));
1239 assert_send(archive.put_start_sync(3, key.clone(), value));
1240 assert_send(archive.get(Identifier::Index(1)));
1241 assert_send(archive.get(Identifier::Key(&key)));
1242 assert_send(archive.has(Identifier::Index(1)));
1243 assert_send(archive.has(Identifier::Key(&key)));
1244 assert_send(archive.sync());
1245 assert_send(archive.start_sync());
1246 }
1247
1248 #[allow(dead_code)]
1249 fn assert_archive_destroy_is_send<T: super::Archive>(archive: T) {
1250 assert_send(archive.destroy());
1251 }
1252
1253 #[allow(dead_code)]
1254 fn assert_multi_archive_futures_are_send<T: super::MultiArchive>(
1255 archive: &mut T,
1256 key: T::Key,
1257 value: T::Value,
1258 ) where
1259 T::Key: Clone,
1260 T::Value: Clone,
1261 {
1262 assert_archive_futures_are_send(archive, key.clone(), value.clone());
1263 assert_send(archive.get_all(1));
1264 assert_send(archive.put_multi(1, key.clone(), value.clone()));
1265 assert_send(archive.put_multi_sync(2, key.clone(), value.clone()));
1266 assert_send(archive.put_multi_start_sync(3, key, value));
1267 }
1268
1269 #[allow(dead_code)]
1270 fn assert_prunable_archive_futures_are_send(
1271 archive: &mut prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1272 key: FixedBytes<64>,
1273 value: i32,
1274 ) {
1275 assert_archive_futures_are_send(archive, key, value);
1276 }
1277
1278 #[allow(dead_code)]
1279 fn assert_prunable_multi_archive_futures_are_send(
1280 archive: &mut prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1281 key: FixedBytes<64>,
1282 value: i32,
1283 ) {
1284 assert_multi_archive_futures_are_send(archive, key, value);
1285 }
1286
1287 #[allow(dead_code)]
1288 fn assert_prunable_archive_destroy_is_send(
1289 archive: prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1290 ) {
1291 assert_archive_destroy_is_send(archive);
1292 }
1293
1294 #[allow(dead_code)]
1295 fn assert_immutable_archive_futures_are_send(
1296 archive: &mut immutable::Archive<Context, FixedBytes<64>, i32>,
1297 key: FixedBytes<64>,
1298 value: i32,
1299 ) {
1300 assert_archive_futures_are_send(archive, key, value);
1301 }
1302
1303 #[allow(dead_code)]
1304 fn assert_immutable_archive_destroy_is_send(
1305 archive: immutable::Archive<Context, FixedBytes<64>, i32>,
1306 ) {
1307 assert_archive_destroy_is_send(archive);
1308 }
1309}