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