1use std::collections::HashMap;
59use std::fs::{self, File};
60use std::io::{BufReader, BufWriter};
61use std::path::{Path, PathBuf};
62use std::sync::Arc;
63use std::time::{SystemTime, UNIX_EPOCH};
64
65use parking_lot::RwLock;
66use serde::{Deserialize, Serialize};
67
68#[derive(Debug, Clone, thiserror::Error)]
74pub enum NamespaceStorageError {
75 #[error("namespace not found: {0}")]
76 NotFound(String),
77
78 #[error("namespace already exists: {0}")]
79 AlreadyExists(String),
80
81 #[error("invalid namespace name: {0}")]
82 InvalidName(String),
83
84 #[error("collection not found: {namespace}/{collection}")]
85 CollectionNotFound { namespace: String, collection: String },
86
87 #[error("collection already exists: {namespace}/{collection}")]
88 CollectionAlreadyExists { namespace: String, collection: String },
89
90 #[error("storage I/O error: {0}")]
91 IoError(String),
92
93 #[error("namespace is read-only: {0}")]
94 ReadOnly(String),
95
96 #[error("namespace registry corrupted: {0}")]
97 RegistryCorrupted(String),
98}
99
100impl From<std::io::Error> for NamespaceStorageError {
101 fn from(e: std::io::Error) -> Self {
102 NamespaceStorageError::IoError(e.to_string())
103 }
104}
105
106pub type Result<T> = std::result::Result<T, NamespaceStorageError>;
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct NamespaceMeta {
115 pub name: String,
117
118 pub display_name: Option<String>,
120
121 pub created_at: u64,
123
124 pub updated_at: u64,
126
127 pub read_only: bool,
129
130 pub labels: HashMap<String, String>,
132
133 #[serde(default)]
135 pub collections: Vec<String>,
136}
137
138impl NamespaceMeta {
139 pub fn new(name: impl Into<String>) -> Self {
141 let now = SystemTime::now()
142 .duration_since(UNIX_EPOCH)
143 .unwrap_or_default()
144 .as_millis() as u64;
145
146 Self {
147 name: name.into(),
148 display_name: None,
149 created_at: now,
150 updated_at: now,
151 read_only: false,
152 labels: HashMap::new(),
153 collections: Vec::new(),
154 }
155 }
156
157 pub fn with_display_name(mut self, name: impl Into<String>) -> Self {
159 self.display_name = Some(name.into());
160 self
161 }
162
163 pub fn with_labels(mut self, labels: HashMap<String, String>) -> Self {
165 self.labels = labels;
166 self
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, Default)]
172pub struct NamespaceRegistry {
173 pub version: u32,
175
176 pub namespaces: Vec<NamespaceEntry>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct NamespaceEntry {
183 pub name: String,
185
186 pub created_at: u64,
188
189 pub active: bool,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct CollectionConfig {
200 pub name: String,
202
203 pub dimension: Option<usize>,
205
206 pub metric: DistanceMetric,
208
209 pub index_config: IndexConfig,
211
212 pub created_at: u64,
214
215 pub frozen: bool,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
221pub enum DistanceMetric {
222 #[default]
223 Cosine,
224 Euclidean,
225 DotProduct,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct IndexConfig {
231 pub m: usize,
233
234 pub ef_construction: usize,
236
237 pub quantization: Option<QuantizationType>,
239}
240
241impl Default for IndexConfig {
242 fn default() -> Self {
243 Self {
244 m: 16,
245 ef_construction: 100,
246 quantization: None,
247 }
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
253pub enum QuantizationType {
254 Scalar, PQ, }
257
258#[derive(Debug, Clone)]
264pub struct NamespaceHandle {
265 pub name: String,
267
268 pub root: PathBuf,
270
271 pub meta: Arc<RwLock<NamespaceMeta>>,
273}
274
275impl NamespaceHandle {
276 pub fn collections_dir(&self) -> PathBuf {
278 self.root.join("collections")
279 }
280
281 pub fn kv_dir(&self) -> PathBuf {
283 self.root.join("kv")
284 }
285
286 pub fn collection_path(&self, collection: &str) -> PathBuf {
288 self.collections_dir().join(collection)
289 }
290
291 pub fn has_collection(&self, collection: &str) -> bool {
293 self.collection_path(collection).exists()
294 }
295
296 pub fn list_collections(&self) -> Result<Vec<String>> {
298 let collections_dir = self.collections_dir();
299 if !collections_dir.exists() {
300 return Ok(Vec::new());
301 }
302
303 let mut collections = Vec::new();
304 for entry in fs::read_dir(&collections_dir)? {
305 let entry = entry?;
306 if entry.file_type()?.is_dir() {
307 if let Some(name) = entry.file_name().to_str() {
308 collections.push(name.to_string());
309 }
310 }
311 }
312 Ok(collections)
313 }
314}
315
316#[derive(Debug, Clone)]
318pub struct CollectionHandle {
319 pub namespace: String,
321
322 pub name: String,
324
325 pub root: PathBuf,
327
328 pub config: Arc<CollectionConfig>,
330}
331
332impl CollectionHandle {
333 pub fn vectors_path(&self) -> PathBuf {
335 self.root.join("vectors.idx")
336 }
337
338 pub fn data_path(&self) -> PathBuf {
340 self.root.join("data.db")
341 }
342
343 pub fn metadata_path(&self) -> PathBuf {
345 self.root.join("metadata.idx")
346 }
347
348 pub fn tombstones_path(&self) -> PathBuf {
350 self.root.join("tombstones.bin")
351 }
352}
353
354pub struct NamespaceRouter {
362 data_dir: PathBuf,
364
365 registry: RwLock<NamespaceRegistry>,
367
368 namespaces: RwLock<HashMap<String, Arc<NamespaceHandle>>>,
370
371 collections: RwLock<HashMap<(String, String), Arc<CollectionHandle>>>,
373}
374
375impl NamespaceRouter {
376 pub fn new(data_dir: impl AsRef<Path>) -> Result<Self> {
378 let data_dir = data_dir.as_ref().to_path_buf();
379
380 fs::create_dir_all(&data_dir)?;
382 fs::create_dir_all(data_dir.join("namespaces"))?;
383 fs::create_dir_all(data_dir.join("_global"))?;
384
385 let registry_path = data_dir.join("_namespaces.meta");
387 let registry = if registry_path.exists() {
388 let file = File::open(®istry_path)?;
389 let reader = BufReader::new(file);
390 serde_json::from_reader(reader)
391 .map_err(|e| NamespaceStorageError::RegistryCorrupted(e.to_string()))?
392 } else {
393 NamespaceRegistry {
394 version: 1,
395 namespaces: Vec::new(),
396 }
397 };
398
399 Ok(Self {
400 data_dir,
401 registry: RwLock::new(registry),
402 namespaces: RwLock::new(HashMap::new()),
403 collections: RwLock::new(HashMap::new()),
404 })
405 }
406
407 fn save_registry(&self) -> Result<()> {
409 let registry_path = self.data_dir.join("_namespaces.meta");
410 let file = File::create(®istry_path)?;
411 let writer = BufWriter::new(file);
412 serde_json::to_writer_pretty(writer, &*self.registry.read())
413 .map_err(|e| NamespaceStorageError::IoError(e.to_string()))?;
414 Ok(())
415 }
416
417 pub fn create_namespace(&self, meta: NamespaceMeta) -> Result<Arc<NamespaceHandle>> {
423 let name = meta.name.clone();
424
425 Self::validate_namespace_name(&name)?;
427
428 {
430 let registry = self.registry.read();
431 if registry.namespaces.iter().any(|e| e.name == name) {
432 return Err(NamespaceStorageError::AlreadyExists(name));
433 }
434 }
435
436 let namespace_dir = self.data_dir.join("namespaces").join(&name);
438 fs::create_dir_all(&namespace_dir)?;
439 fs::create_dir_all(namespace_dir.join("collections"))?;
440 fs::create_dir_all(namespace_dir.join("kv"))?;
441
442 let meta_path = namespace_dir.join("_meta.json");
444 let file = File::create(&meta_path)?;
445 let writer = BufWriter::new(file);
446 serde_json::to_writer_pretty(writer, &meta)
447 .map_err(|e| NamespaceStorageError::IoError(e.to_string()))?;
448
449 {
451 let mut registry = self.registry.write();
452 registry.namespaces.push(NamespaceEntry {
453 name: name.clone(),
454 created_at: meta.created_at,
455 active: true,
456 });
457 }
458 self.save_registry()?;
459
460 let handle = Arc::new(NamespaceHandle {
462 name: name.clone(),
463 root: namespace_dir,
464 meta: Arc::new(RwLock::new(meta)),
465 });
466
467 self.namespaces.write().insert(name, handle.clone());
468
469 Ok(handle)
470 }
471
472 pub fn get_namespace(&self, name: &str) -> Result<Arc<NamespaceHandle>> {
474 if let Some(handle) = self.namespaces.read().get(name) {
476 return Ok(handle.clone());
477 }
478
479 {
481 let registry = self.registry.read();
482 if !registry.namespaces.iter().any(|e| e.name == name && e.active) {
483 return Err(NamespaceStorageError::NotFound(name.to_string()));
484 }
485 }
486
487 let namespace_dir = self.data_dir.join("namespaces").join(name);
489 if !namespace_dir.exists() {
490 return Err(NamespaceStorageError::NotFound(name.to_string()));
491 }
492
493 let meta_path = namespace_dir.join("_meta.json");
494 let meta: NamespaceMeta = if meta_path.exists() {
495 let file = File::open(&meta_path)?;
496 let reader = BufReader::new(file);
497 serde_json::from_reader(reader)
498 .map_err(|e| NamespaceStorageError::RegistryCorrupted(e.to_string()))?
499 } else {
500 NamespaceMeta::new(name)
501 };
502
503 let handle = Arc::new(NamespaceHandle {
504 name: name.to_string(),
505 root: namespace_dir,
506 meta: Arc::new(RwLock::new(meta)),
507 });
508
509 self.namespaces.write().insert(name.to_string(), handle.clone());
510
511 Ok(handle)
512 }
513
514 pub fn list_namespaces(&self) -> Vec<String> {
516 self.registry
517 .read()
518 .namespaces
519 .iter()
520 .filter(|e| e.active)
521 .map(|e| e.name.clone())
522 .collect()
523 }
524
525 pub fn delete_namespace(&self, name: &str) -> Result<()> {
527 {
528 let mut registry = self.registry.write();
529 let entry = registry
530 .namespaces
531 .iter_mut()
532 .find(|e| e.name == name)
533 .ok_or_else(|| NamespaceStorageError::NotFound(name.to_string()))?;
534 entry.active = false;
535 }
536
537 self.save_registry()?;
538 self.namespaces.write().remove(name);
539
540 self.collections
542 .write()
543 .retain(|(ns, _), _| ns != name);
544
545 Ok(())
546 }
547
548 fn validate_namespace_name(name: &str) -> Result<()> {
550 if name.is_empty() {
551 return Err(NamespaceStorageError::InvalidName(
552 "namespace name cannot be empty".to_string(),
553 ));
554 }
555
556 if name.len() > 256 {
557 return Err(NamespaceStorageError::InvalidName(
558 "namespace name too long (max 256 chars)".to_string(),
559 ));
560 }
561
562 let first = name.chars().next().unwrap();
564 if !first.is_alphanumeric() {
565 return Err(NamespaceStorageError::InvalidName(format!(
566 "namespace name must start with alphanumeric, got '{}'",
567 first
568 )));
569 }
570
571 for ch in name.chars() {
573 if !ch.is_alphanumeric() && ch != '_' && ch != '-' && ch != '.' {
574 return Err(NamespaceStorageError::InvalidName(format!(
575 "invalid character '{}' in namespace name",
576 ch
577 )));
578 }
579 }
580
581 let reserved = ["_global", "_namespaces", "_meta", "_system"];
583 if reserved.contains(&name) {
584 return Err(NamespaceStorageError::InvalidName(format!(
585 "'{}' is a reserved name",
586 name
587 )));
588 }
589
590 Ok(())
591 }
592
593 pub fn create_collection(
599 &self,
600 namespace: &str,
601 config: CollectionConfig,
602 ) -> Result<Arc<CollectionHandle>> {
603 let ns_handle = self.get_namespace(namespace)?;
604
605 if ns_handle.meta.read().read_only {
607 return Err(NamespaceStorageError::ReadOnly(namespace.to_string()));
608 }
609
610 let collection_name = config.name.clone();
611 let collection_dir = ns_handle.collection_path(&collection_name);
612
613 if collection_dir.exists() {
615 return Err(NamespaceStorageError::CollectionAlreadyExists {
616 namespace: namespace.to_string(),
617 collection: collection_name,
618 });
619 }
620
621 fs::create_dir_all(&collection_dir)?;
623
624 let frozen_config = CollectionConfig {
626 frozen: true,
627 ..config
628 };
629
630 let config_path = collection_dir.join("config.json");
631 let file = File::create(&config_path)?;
632 let writer = BufWriter::new(file);
633 serde_json::to_writer_pretty(writer, &frozen_config)
634 .map_err(|e| NamespaceStorageError::IoError(e.to_string()))?;
635
636 {
638 let mut meta = ns_handle.meta.write();
639 meta.collections.push(collection_name.clone());
640 meta.updated_at = SystemTime::now()
641 .duration_since(UNIX_EPOCH)
642 .unwrap_or_default()
643 .as_millis() as u64;
644 }
645
646 let ns_meta_path = ns_handle.root.join("_meta.json");
648 let file = File::create(&ns_meta_path)?;
649 let writer = BufWriter::new(file);
650 serde_json::to_writer_pretty(writer, &*ns_handle.meta.read())
651 .map_err(|e| NamespaceStorageError::IoError(e.to_string()))?;
652
653 let handle = Arc::new(CollectionHandle {
655 namespace: namespace.to_string(),
656 name: collection_name.clone(),
657 root: collection_dir,
658 config: Arc::new(frozen_config),
659 });
660
661 self.collections
662 .write()
663 .insert((namespace.to_string(), collection_name), handle.clone());
664
665 Ok(handle)
666 }
667
668 pub fn get_collection(
670 &self,
671 namespace: &str,
672 collection: &str,
673 ) -> Result<Arc<CollectionHandle>> {
674 let key = (namespace.to_string(), collection.to_string());
675
676 if let Some(handle) = self.collections.read().get(&key) {
678 return Ok(handle.clone());
679 }
680
681 let ns_handle = self.get_namespace(namespace)?;
682 let collection_dir = ns_handle.collection_path(collection);
683
684 if !collection_dir.exists() {
685 return Err(NamespaceStorageError::CollectionNotFound {
686 namespace: namespace.to_string(),
687 collection: collection.to_string(),
688 });
689 }
690
691 let config_path = collection_dir.join("config.json");
693 let config: CollectionConfig = if config_path.exists() {
694 let file = File::open(&config_path)?;
695 let reader = BufReader::new(file);
696 serde_json::from_reader(reader)
697 .map_err(|e| NamespaceStorageError::RegistryCorrupted(e.to_string()))?
698 } else {
699 CollectionConfig {
701 name: collection.to_string(),
702 dimension: None,
703 metric: DistanceMetric::Cosine,
704 index_config: IndexConfig::default(),
705 created_at: 0,
706 frozen: true,
707 }
708 };
709
710 let handle = Arc::new(CollectionHandle {
711 namespace: namespace.to_string(),
712 name: collection.to_string(),
713 root: collection_dir,
714 config: Arc::new(config),
715 });
716
717 self.collections.write().insert(key, handle.clone());
718
719 Ok(handle)
720 }
721
722 pub fn delete_collection(&self, namespace: &str, collection: &str) -> Result<()> {
724 let ns_handle = self.get_namespace(namespace)?;
725
726 if ns_handle.meta.read().read_only {
728 return Err(NamespaceStorageError::ReadOnly(namespace.to_string()));
729 }
730
731 let collection_dir = ns_handle.collection_path(collection);
732 if !collection_dir.exists() {
733 return Err(NamespaceStorageError::CollectionNotFound {
734 namespace: namespace.to_string(),
735 collection: collection.to_string(),
736 });
737 }
738
739 self.collections
741 .write()
742 .remove(&(namespace.to_string(), collection.to_string()));
743
744 {
746 let mut meta = ns_handle.meta.write();
747 meta.collections.retain(|c| c != collection);
748 meta.updated_at = SystemTime::now()
749 .duration_since(UNIX_EPOCH)
750 .unwrap_or_default()
751 .as_millis() as u64;
752 }
753
754 let ns_meta_path = ns_handle.root.join("_meta.json");
756 let file = File::create(&ns_meta_path)?;
757 let writer = BufWriter::new(file);
758 serde_json::to_writer_pretty(writer, &*ns_handle.meta.read())
759 .map_err(|e| NamespaceStorageError::IoError(e.to_string()))?;
760
761 fs::remove_dir_all(&collection_dir)?;
763
764 Ok(())
765 }
766
767 pub fn resolve(
771 &self,
772 namespace: &str,
773 collection: &str,
774 ) -> Result<Arc<CollectionHandle>> {
775 self.get_collection(namespace, collection)
776 }
777}
778
779pub struct PrefixIterator<I> {
788 inner: I,
789 prefix: Vec<u8>,
790 exhausted: bool,
791}
792
793impl<I> PrefixIterator<I> {
794 pub fn new(inner: I, prefix: Vec<u8>) -> Self {
796 Self {
797 inner,
798 prefix,
799 exhausted: false,
800 }
801 }
802
803 pub fn prefix(&self) -> &[u8] {
805 &self.prefix
806 }
807}
808
809impl<I, K, V> Iterator for PrefixIterator<I>
810where
811 I: Iterator<Item = (K, V)>,
812 K: AsRef<[u8]>,
813{
814 type Item = (K, V);
815
816 fn next(&mut self) -> Option<Self::Item> {
817 if self.exhausted {
818 return None;
819 }
820
821 match self.inner.next() {
822 Some((key, value)) => {
823 if key.as_ref().starts_with(&self.prefix) {
824 Some((key, value))
825 } else {
826 self.exhausted = true;
829 None
830 }
831 }
832 None => {
833 self.exhausted = true;
834 None
835 }
836 }
837 }
838}
839
840pub fn next_prefix(prefix: &[u8]) -> Option<Vec<u8>> {
852 if prefix.is_empty() {
853 return None;
854 }
855
856 let mut result = prefix.to_vec();
857
858 while let Some(&last) = result.last() {
860 if last == 0xFF {
861 result.pop();
862 } else {
863 *result.last_mut().unwrap() += 1;
865 return Some(result);
866 }
867 }
868
869 None
871}
872
873#[cfg(test)]
878mod tests {
879 use super::*;
880 use tempfile::TempDir;
881
882 fn setup_router() -> (TempDir, NamespaceRouter) {
883 let temp_dir = TempDir::new().unwrap();
884 let router = NamespaceRouter::new(temp_dir.path()).unwrap();
885 (temp_dir, router)
886 }
887
888 #[test]
889 fn test_create_namespace() {
890 let (_temp, router) = setup_router();
891
892 let meta = NamespaceMeta::new("tenant_a")
893 .with_display_name("Tenant A")
894 .with_labels([("env".to_string(), "production".to_string())].into());
895
896 let handle = router.create_namespace(meta).unwrap();
897 assert_eq!(handle.name, "tenant_a");
898 assert!(handle.root.exists());
899 assert!(handle.collections_dir().exists());
900 }
901
902 #[test]
903 fn test_namespace_already_exists() {
904 let (_temp, router) = setup_router();
905
906 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
907
908 let result = router.create_namespace(NamespaceMeta::new("tenant_a"));
909 assert!(matches!(result, Err(NamespaceStorageError::AlreadyExists(_))));
910 }
911
912 #[test]
913 fn test_get_namespace() {
914 let (_temp, router) = setup_router();
915
916 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
917
918 let handle = router.get_namespace("tenant_a").unwrap();
919 assert_eq!(handle.name, "tenant_a");
920 }
921
922 #[test]
923 fn test_namespace_not_found() {
924 let (_temp, router) = setup_router();
925
926 let result = router.get_namespace("nonexistent");
927 assert!(matches!(result, Err(NamespaceStorageError::NotFound(_))));
928 }
929
930 #[test]
931 fn test_list_namespaces() {
932 let (_temp, router) = setup_router();
933
934 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
935 router.create_namespace(NamespaceMeta::new("tenant_b")).unwrap();
936
937 let mut namespaces = router.list_namespaces();
938 namespaces.sort();
939
940 assert_eq!(namespaces, vec!["tenant_a", "tenant_b"]);
941 }
942
943 #[test]
944 fn test_delete_namespace() {
945 let (_temp, router) = setup_router();
946
947 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
948 router.delete_namespace("tenant_a").unwrap();
949
950 let namespaces = router.list_namespaces();
951 assert!(!namespaces.contains(&"tenant_a".to_string()));
952 }
953
954 #[test]
955 fn test_create_collection() {
956 let (_temp, router) = setup_router();
957
958 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
959
960 let config = CollectionConfig {
961 name: "documents".to_string(),
962 dimension: Some(384),
963 metric: DistanceMetric::Cosine,
964 index_config: IndexConfig::default(),
965 created_at: 0,
966 frozen: false,
967 };
968
969 let handle = router.create_collection("tenant_a", config).unwrap();
970 assert_eq!(handle.name, "documents");
971 assert!(handle.root.exists());
972 assert!(handle.config.frozen); }
974
975 #[test]
976 fn test_get_collection() {
977 let (_temp, router) = setup_router();
978
979 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
980
981 let config = CollectionConfig {
982 name: "documents".to_string(),
983 dimension: Some(384),
984 metric: DistanceMetric::Cosine,
985 index_config: IndexConfig::default(),
986 created_at: 0,
987 frozen: false,
988 };
989
990 router.create_collection("tenant_a", config).unwrap();
991
992 let handle = router.get_collection("tenant_a", "documents").unwrap();
993 assert_eq!(handle.name, "documents");
994 assert_eq!(handle.namespace, "tenant_a");
995 }
996
997 #[test]
998 fn test_resolve() {
999 let (_temp, router) = setup_router();
1000
1001 router.create_namespace(NamespaceMeta::new("tenant_a")).unwrap();
1002
1003 let config = CollectionConfig {
1004 name: "documents".to_string(),
1005 dimension: Some(384),
1006 metric: DistanceMetric::Cosine,
1007 index_config: IndexConfig::default(),
1008 created_at: 0,
1009 frozen: false,
1010 };
1011
1012 router.create_collection("tenant_a", config).unwrap();
1013
1014 let handle = router.resolve("tenant_a", "documents").unwrap();
1016 assert_eq!(handle.vectors_path(), handle.root.join("vectors.idx"));
1017 }
1018
1019 #[test]
1020 fn test_invalid_namespace_names() {
1021 let (_temp, router) = setup_router();
1022
1023 assert!(router.create_namespace(NamespaceMeta::new("")).is_err());
1025
1026 assert!(router.create_namespace(NamespaceMeta::new("-bad")).is_err());
1028
1029 assert!(router.create_namespace(NamespaceMeta::new("bad name")).is_err());
1031
1032 assert!(router.create_namespace(NamespaceMeta::new("_global")).is_err());
1034 }
1035
1036 #[test]
1037 fn test_next_prefix() {
1038 assert_eq!(next_prefix(b"abc"), Some(b"abd".to_vec()));
1039 assert_eq!(next_prefix(b"ab\xff"), Some(b"ac".to_vec()));
1040 assert_eq!(next_prefix(b"\xff\xff\xff"), None);
1041 assert_eq!(next_prefix(b""), None);
1042 assert_eq!(next_prefix(b"tenant_a/"), Some(b"tenant_a0".to_vec()));
1043 }
1044
1045 #[test]
1046 fn test_prefix_iterator() {
1047 let data = vec![
1048 (b"tenant_a/doc1".to_vec(), b"v1".to_vec()),
1049 (b"tenant_a/doc2".to_vec(), b"v2".to_vec()),
1050 (b"tenant_b/doc1".to_vec(), b"v3".to_vec()), ];
1052
1053 let iter = PrefixIterator::new(data.into_iter(), b"tenant_a/".to_vec());
1054 let results: Vec<_> = iter.collect();
1055
1056 assert_eq!(results.len(), 2);
1057 assert!(results[0].0.starts_with(b"tenant_a/"));
1058 assert!(results[1].0.starts_with(b"tenant_a/"));
1059 }
1060}