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