1use super::*;
2
3impl UnifiedStore {
4 pub(crate) fn insert_vault_version(
8 &self,
9 collection: &str,
10 entity: UnifiedEntity,
11 metadata: Metadata,
12 ) -> Result<(), StoreError> {
13 assert!(matches!(entity.data, EntityData::Row(_)));
14 let manager = self
15 .get_collection(collection)
16 .ok_or_else(|| StoreError::CollectionNotFound(collection.into()))?;
17 self.pending_vault_versions.write().insert(entity.id);
18 let id = manager.insert(entity)?;
19 self.register_entity_id(id);
20 if !metadata.fields.is_empty() {
21 manager.set_metadata(id, metadata)?;
22 }
23 let entity = manager
24 .get(id)
25 .expect("inserted vault version remains in memory");
26 self.persist_entities_to_pager_wal_only(collection, &[entity])
27 }
28
29 pub(crate) fn persist_entities_to_pager(
30 &self,
31 collection: &str,
32 entities: &[UnifiedEntity],
33 ) -> Result<(), StoreError> {
34 self.persist_entities_impl(collection, entities, false)
35 }
36
37 pub(crate) fn persist_entity_refs_to_pager(
41 &self,
42 collection: &str,
43 entities: &[&UnifiedEntity],
44 ) -> Result<(), StoreError> {
45 self.persist_entity_refs_impl(collection, entities, false)
46 }
47
48 pub(crate) fn persist_entity_refs_to_pager_wal_only(
51 &self,
52 collection: &str,
53 entities: &[&UnifiedEntity],
54 ) -> Result<(), StoreError> {
55 self.persist_entity_refs_impl(collection, entities, true)
56 }
57
58 fn persist_entity_refs_impl(
59 &self,
60 collection: &str,
61 entities: &[&UnifiedEntity],
62 skip_btree: bool,
63 ) -> Result<(), StoreError> {
64 if entities.is_empty() {
65 return Ok(());
66 }
67 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
68 return Ok(());
69 }
70 let fv = self.format_version();
71 let manager = self
72 .get_collection(collection)
73 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
74 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
75 .iter()
76 .map(|entity| {
77 let metadata = manager.get_metadata(entity.id);
78 (
79 entity.id.raw().to_be_bytes().to_vec(),
80 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
81 )
82 })
83 .collect();
84 serialized.sort_by(|a, b| a.0.cmp(&b.0));
85
86 if !skip_btree {
87 let Some(pager) = &self.pager else {
88 let records: Vec<Vec<u8>> =
89 serialized.into_iter().map(|(_id, record)| record).collect();
90 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
91 collection: collection.to_string(),
92 records,
93 }])?;
94 return Ok(());
95 };
96 let mut btree_indices = self.btree_indices.write();
97 let btree = btree_indices
98 .entry(collection.to_string())
99 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
100 let root_before = btree.root_page_id();
101 btree.upsert_batch_sorted(&serialized).map_err(|e| {
102 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
103 })?;
104 let root_after = btree.root_page_id();
105 drop(btree_indices);
106 if root_before != root_after {
107 self.mark_paged_registry_dirty();
108 }
109 }
110 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
111 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
112 collection: collection.to_string(),
113 records,
114 }])?;
115 Ok(())
116 }
117
118 pub(crate) fn persist_entities_to_pager_wal_only(
130 &self,
131 collection: &str,
132 entities: &[UnifiedEntity],
133 ) -> Result<(), StoreError> {
134 self.persist_entities_impl(collection, entities, true)
135 }
136
137 fn persist_entities_impl(
138 &self,
139 collection: &str,
140 entities: &[UnifiedEntity],
141 skip_btree: bool,
142 ) -> Result<(), StoreError> {
143 if entities.is_empty() {
144 return Ok(());
145 }
146
147 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
148 return Ok(());
149 }
150
151 let fv = self.format_version();
152 let manager = self
153 .get_collection(collection)
154 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
155 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
156 .iter()
157 .map(|entity| {
158 let metadata = manager.get_metadata(entity.id);
159 (
160 entity.id.raw().to_be_bytes().to_vec(),
161 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
162 )
163 })
164 .collect();
165 serialized.sort_by(|a, b| a.0.cmp(&b.0));
167
168 if !skip_btree {
169 let Some(pager) = &self.pager else {
170 let records: Vec<Vec<u8>> =
171 serialized.into_iter().map(|(_id, record)| record).collect();
172 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
173 collection: collection.to_string(),
174 records,
175 }])?;
176 return Ok(());
177 };
178 let mut btree_indices = self.btree_indices.write();
179 let btree = btree_indices
180 .entry(collection.to_string())
181 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
182 let root_before = btree.root_page_id();
183
184 btree.upsert_batch_sorted(&serialized).map_err(|e| {
188 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
189 })?;
190 let root_after = btree.root_page_id();
191 drop(btree_indices);
192 if root_before != root_after {
193 self.mark_paged_registry_dirty();
194 }
195 }
196 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
206 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
207 collection: collection.to_string(),
208 records,
209 }])?;
210
211 Ok(())
212 }
213
214 pub(crate) fn update_graph_label_index(
216 &self,
217 collection: &str,
218 label: &str,
219 entity_id: EntityId,
220 ) {
221 let key = (collection.to_string(), label.to_string());
222 let mut idx = self.graph_label_index.write();
223 idx.entry(key).or_default().push(entity_id);
224 }
225
226 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
228 let mut idx = self.graph_label_index.write();
229 for ((col, _), ids) in idx.iter_mut() {
230 if col == collection {
231 ids.retain(|&id| id != entity_id);
232 }
233 }
234 idx.retain(|_, ids| !ids.is_empty());
236 }
237
238 pub(crate) fn remove_from_graph_label_index_batch(
239 &self,
240 collection: &str,
241 entity_ids: &[EntityId],
242 ) {
243 if entity_ids.is_empty() {
244 return;
245 }
246 let id_set: std::collections::HashSet<EntityId> = entity_ids.iter().copied().collect();
247 let mut idx = self.graph_label_index.write();
248 for ((col, _), ids) in idx.iter_mut() {
249 if col == collection {
250 ids.retain(|id| !id_set.contains(id));
251 }
252 }
253 idx.retain(|_, ids| !ids.is_empty());
254 }
255
256 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
258 let idx = self.graph_label_index.read();
259 idx.iter()
260 .filter(|((_, l), _)| l == label)
261 .flat_map(|(_, ids)| ids.iter().copied())
262 .collect()
263 }
264
265 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
267 let idx = self.graph_label_index.read();
268 idx.get(&(collection.to_string(), label.to_string()))
269 .cloned()
270 .unwrap_or_default()
271 }
272
273 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
274 let name = name.into();
275 let mut collections = self.collections.write();
276
277 if collections.contains_key(&name) {
278 return Err(StoreError::CollectionExists(name));
279 }
280
281 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
282 collections.insert(name.clone(), Arc::new(manager));
283 drop(collections);
284
285 let create = match self.prepare_operational_collection_create(&name) {
286 Ok(create) => create,
287 Err(err) => {
288 let mut collections = self.collections.write();
289 collections.remove(&name);
290 return Err(err);
291 }
292 };
293 if let Some(create) = create {
294 if let Err(err) = create.publish().map_err(StoreError::Io) {
295 let mut collections = self.collections.write();
296 collections.remove(&name);
297 return Err(err);
298 }
299 }
300
301 self.mark_paged_registry_dirty();
302 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
303
304 Ok(())
305 }
306
307 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
309 let name = name.into();
310 {
312 let collections = self.collections.read();
313 if let Some(manager) = collections.get(&name) {
314 return Arc::clone(manager);
315 }
316 }
317 let mut collections = self.collections.write();
319 if let Some(manager) = collections.get(&name) {
321 return Arc::clone(manager);
322 }
323 let manager = Arc::new(SegmentManager::with_config(
324 &name,
325 self.config.manager_config.clone(),
326 ));
327 collections.insert(name, Arc::clone(&manager));
328 self.mark_paged_registry_dirty();
329 manager
330 }
331
332 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
334 self.collections.read().get(name).map(Arc::clone)
335 }
336
337 pub fn context_index(&self) -> &ContextIndex {
339 &self.context_index
340 }
341
342 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
348 let mut map = self.replayed_turbo_inserts.lock();
349 map.remove(collection)
350 }
351
352 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
353 let mut deltas = self.replayed_probabilistic_deltas.lock();
354 std::mem::take(&mut *deltas)
355 }
356
357 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
364 let manager = self.get_or_create_collection("red_config");
365 let mut pairs = Vec::new();
366 flatten_config_json(prefix, json, &mut pairs);
367 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
369 .iter()
370 .map(|(key, _)| (key.clone(), Vec::new()))
371 .collect();
372 for entity in manager.query_all(|_| true) {
373 if let EntityData::Row(row) = &entity.data {
374 let Some(named) = row.named.as_ref() else {
375 continue;
376 };
377 if let Some(reddb_types::Value::Text(key)) = named.get("key") {
378 if let Some(rows) = previous.get_mut(key.as_ref()) {
379 rows.push((entity.id, named.get("value").cloned()));
380 }
381 }
382 }
383 }
384 let mut saved = 0;
385 for (key, value) in pairs {
386 let stale = previous.remove(&key).unwrap_or_default();
387 if let [(_, Some(current))] = stale.as_slice() {
391 if *current == value {
392 saved += 1;
393 continue;
394 }
395 }
396 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
397 let entity = UnifiedEntity::new(
401 EntityId::new(0),
402 EntityKind::TableRow {
403 table: Arc::from("red_config"),
404 row_id: 0,
405 },
406 EntityData::Row(RowData {
407 columns: Vec::new(),
408 named: Some(
409 [
410 ("key".to_string(), reddb_types::Value::text(key)),
411 ("value".to_string(), value),
412 ]
413 .into_iter()
414 .collect(),
415 ),
416 schema: None,
417 }),
418 );
419 if self.insert_auto("red_config", entity).is_ok() {
420 saved += 1;
421 if !stale.is_empty() {
422 let _ = self.delete_batch("red_config", &stale);
423 }
424 }
425 }
426 saved
427 }
428
429 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
433 let manager = self.get_collection("red_config")?;
434 let mut latest: Option<(u64, reddb_types::Value)> = None;
435 for entity in manager.query_all(|_| true) {
436 if let EntityData::Row(row) = &entity.data {
437 if let Some(named) = &row.named {
438 let key_matches = named
439 .get("key")
440 .and_then(|v| match v {
441 reddb_types::Value::Text(s) => Some(s.as_ref() == key),
442 _ => None,
443 })
444 .unwrap_or(false);
445 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
446 if let Some(value) = named.get("value") {
447 latest = Some((entity.id.raw(), value.clone()));
448 }
449 }
450 }
451 }
452 }
453 latest.map(|(_, value)| value)
454 }
455
456 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
461 *self.aux_metadata.write() = bytes;
462 }
463
464 pub fn aux_metadata(&self) -> Vec<u8> {
466 self.aux_metadata.read().clone()
467 }
468
469 pub fn list_collections(&self) -> Vec<String> {
471 self.collections.read().keys().cloned().collect()
472 }
473
474 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
476 let drop = self.prepare_operational_collection_drop(name)?;
477 let manager = {
478 let mut collections = self.collections.write();
479
480 collections
481 .remove(name)
482 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
483 };
484
485 let entities = manager.query_all(|_| true);
486 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
487
488 for entity_id in &entity_ids {
489 self.context_index.remove_entity(*entity_id);
490 let _ = self.unindex_cross_refs(*entity_id);
491 }
492
493 self.btree_indices.write().remove(name);
494
495 self.entity_cache.retain(|entity_id, (collection, _)| {
496 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
497 });
498
499 self.cross_refs.write().retain(|source_id, refs| {
500 refs.retain(|(target_id, _, target_collection)| {
501 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
502 });
503 !entity_ids.iter().any(|id| id == source_id)
504 });
505
506 self.reverse_refs.write().retain(|target_id, refs| {
507 refs.retain(|(source_id, _, source_collection)| {
508 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
509 });
510 !entity_ids.iter().any(|id| id == target_id)
511 });
512
513 self.mark_paged_registry_dirty();
514 self.finish_paged_write([StoreWalAction::DropCollection {
515 name: name.to_string(),
516 }])?;
517 if let Some(drop) = drop {
518 drop.finish().map_err(StoreError::Io)?;
519 }
520
521 Ok(())
522 }
523
524 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
526 let manager = self
527 .get_collection(collection)
528 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
529
530 let mut entity = entity;
531 if entity.id.raw() == 0 {
532 entity.id = self.next_entity_id();
533 } else {
534 self.register_entity_id(entity.id);
535 }
536 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
538 if *row_id == 0 {
539 *row_id = manager.next_row_id();
540 } else {
541 manager.register_row_id(*row_id);
542 }
543 }
544 entity.ensure_table_logical_id();
545 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
547 {
548 Some(node.label.clone())
549 } else {
550 None
551 };
552
553 let id = manager.insert(entity)?;
554 self.register_entity_id(id);
555
556 if let Some(ref label) = graph_node_label {
558 self.update_graph_label_index(collection, label, id);
559 }
560
561 let mut registry_dirty = false;
563 if let Some(pager) = &self.pager {
564 if let Some(entity) = manager.get(id) {
565 let mut btree_indices = self.btree_indices.write();
566 let btree = btree_indices
567 .entry(collection.to_string())
568 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
569 let root_before = btree.root_page_id();
570
571 let key = id.raw().to_be_bytes();
572 let metadata = manager.get_metadata(id);
573 let value = Self::serialize_entity_record(
574 &entity,
575 metadata.as_ref(),
576 self.format_version(),
577 );
578 let _ = btree.insert(&key, &value);
580 registry_dirty = root_before != btree.root_page_id();
581 }
582 }
583
584 if self.config.auto_index_refs {
586 if let Some(entity) = manager.get(id) {
587 self.index_cross_refs(&entity, collection)?;
588 }
589 }
590
591 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
595 let actions = manager
596 .get(id)
597 .map(|entity| {
598 let metadata = manager.get_metadata(id);
599 vec![StoreWalAction::upsert_entity(
600 collection,
601 &entity,
602 metadata.as_ref(),
603 self.format_version(),
604 )]
605 })
606 .unwrap_or_default();
607 if registry_dirty {
608 self.mark_paged_registry_dirty();
609 }
610 self.finish_paged_write(actions)?;
611 }
612
613 Ok(id)
614 }
615
616 pub fn bulk_insert(
621 &self,
622 collection: &str,
623 mut entities: Vec<UnifiedEntity>,
624 ) -> Result<Vec<EntityId>, StoreError> {
625 let trace = matches!(
629 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
630 Some("1") | Some("true") | Some("on")
631 );
632 let t_start = std::time::Instant::now();
633 let n = entities.len();
634 let manager = self.get_or_create_collection(collection);
635 let t_get_coll = t_start.elapsed();
636
637 let t0 = std::time::Instant::now();
646 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
647 let n_missing_row_ids = entities
648 .iter()
649 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
650 .count() as u64;
651 let mut entity_id_range = if n_missing_entity_ids > 0 {
652 self.reserve_entity_ids(n_missing_entity_ids)
653 } else {
654 0..0
655 };
656 let mut row_id_range = if n_missing_row_ids > 0 {
657 manager.reserve_row_ids(n_missing_row_ids)
658 } else {
659 0..0
660 };
661 for entity in &mut entities {
662 if entity.id.raw() == 0 {
663 let next = entity_id_range
664 .next()
665 .expect("reserved entity-id range exhausted");
666 entity.id = EntityId::new(next);
667 } else {
668 self.register_entity_id(entity.id);
669 }
670 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
671 if *row_id == 0 {
672 *row_id = row_id_range
673 .next()
674 .expect("reserved row-id range exhausted");
675 } else {
676 manager.register_row_id(*row_id);
677 }
678 }
679 entity.ensure_table_logical_id();
680 }
681 let t_assign_ids = t0.elapsed();
682
683 let graph_labels: Vec<Option<(String, EntityId)>> = entities
685 .iter()
686 .map(|e| {
687 if let EntityKind::GraphNode(ref node) = e.kind {
688 Some((node.label.clone(), e.id))
689 } else {
690 None
691 }
692 })
693 .collect();
694
695 let t0 = std::time::Instant::now();
702 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
703 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
704 let fv = self.format_version();
705 let serial_map = |e: &UnifiedEntity| {
706 (
707 e.id.raw().to_be_bytes().to_vec(),
708 Self::serialize_entity_record(e, None, fv),
709 )
710 };
711 if entities.len() >= 512 {
717 use rayon::prelude::*;
718 Some(entities.par_iter().map(serial_map).collect())
719 } else {
720 Some(entities.iter().map(serial_map).collect())
721 }
722 } else {
723 None
724 };
725 let t_serialize = t0.elapsed();
726
727 let t0 = std::time::Instant::now();
729 let ids = manager.bulk_insert(entities)?;
730 let t_manager = t0.elapsed();
731 for id in &ids {
732 self.register_entity_id(*id);
733 }
734
735 for (label, entity_id) in graph_labels.iter().flatten() {
737 self.update_graph_label_index(collection, label, *entity_id);
738 }
739
740 let skip_btree_requested = matches!(
750 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
751 .ok()
752 .as_deref(),
753 Some("1") | Some("true") | Some("on")
754 );
755 let skip_btree = skip_btree_requested && self.pager.is_none();
758 if skip_btree_requested && !skip_btree {
759 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
761 IGNORED.get_or_init(|| {
762 tracing::warn!(
763 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
764 active — flag ignored; bulk inserts will be persisted normally"
765 );
766 });
767 } else if skip_btree {
768 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
770 WARNED.get_or_init(|| {
771 tracing::warn!(
772 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
773 bulk inserts NOT durable; data will be lost on restart"
774 );
775 });
776 }
777
778 let mut t_btree_lock = std::time::Duration::ZERO;
782 let mut t_btree_insert = std::time::Duration::ZERO;
783 let mut t_flush = std::time::Duration::ZERO;
784 if !skip_btree {
785 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
786 let t0 = std::time::Instant::now();
787 let mut btree_indices = self.btree_indices.write();
788 let btree = btree_indices
789 .entry(collection.to_string())
790 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
791 let root_before = btree.root_page_id();
792 t_btree_lock = t0.elapsed();
793
794 let t0 = std::time::Instant::now();
795 let _ = btree.bulk_insert_sorted(batch);
796 t_btree_insert = t0.elapsed();
797 let registry_dirty = root_before != btree.root_page_id();
798
799 let t0 = std::time::Instant::now();
800 if registry_dirty {
801 self.mark_paged_registry_dirty();
802 }
803 t_flush = t0.elapsed();
804 }
805 }
806
807 let actions = serialized
813 .map(|batch| {
814 let records: Vec<Vec<u8>> =
815 batch.into_iter().map(|(_key, record)| record).collect();
816 vec![StoreWalAction::BulkUpsertEntityRecords {
817 collection: collection.to_string(),
818 records,
819 }]
820 })
821 .unwrap_or_default();
822 self.finish_paged_write(actions)?;
823
824 if trace {
825 tracing::debug!(
826 n,
827 total = ?t_start.elapsed(),
828 get_coll = ?t_get_coll,
829 assign = ?t_assign_ids,
830 serialize = ?t_serialize,
831 manager = ?t_manager,
832 btree_lock = ?t_btree_lock,
833 btree = ?t_btree_insert,
834 flush = ?t_flush,
835 "bulk_insert timing"
836 );
837 }
838
839 Ok(ids)
840 }
841
842 pub fn insert_auto(
844 &self,
845 collection: &str,
846 entity: UnifiedEntity,
847 ) -> Result<EntityId, StoreError> {
848 let manager = self.get_or_create_collection(collection);
849 let mut entity = entity;
850 if entity.id.raw() == 0 {
851 entity.id = self.next_entity_id();
852 } else {
853 self.register_entity_id(entity.id);
854 }
855 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
857 if *row_id == 0 {
858 *row_id = manager.next_row_id();
859 } else {
860 manager.register_row_id(*row_id);
861 }
862 }
863 entity.ensure_table_logical_id();
864
865 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
867 {
868 Some(node.label.clone())
869 } else {
870 None
871 };
872 self.context_index.index_entity(collection, &entity);
874
875 let id_for_serialize = entity.id;
890 let serialized_record: Option<Vec<u8>> =
891 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
892 Some(Self::serialize_entity_record(
893 &entity,
894 None,
895 self.format_version(),
896 ))
897 } else {
898 None
899 };
900 if self.config.auto_index_refs {
901 self.index_cross_refs(&entity, collection)?;
902 }
903
904 let id = manager.insert(entity)?;
905 debug_assert_eq!(id, id_for_serialize);
906 if let Some(ref label) = graph_node_label {
914 self.update_graph_label_index(collection, label, id);
915 }
916
917 let mut registry_dirty = false;
918 if let (Some(_pager), Some(record)) = (&self.pager, serialized_record.as_ref()) {
919 if let Some(btree) = self.get_or_create_btree(collection) {
920 let root_before = btree.root_page_id();
921
922 let key = id.raw().to_be_bytes();
923 btree.insert(&key, record).map_err(|e| {
924 StoreError::Io(std::io::Error::other(format!(
925 "B-tree insert error while inserting '{collection}'/{id}: {e}"
926 )))
927 })?;
928 registry_dirty = root_before != btree.root_page_id();
929 }
930 }
931
932 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
936 let actions = serialized_record
937 .map(|record| {
938 vec![StoreWalAction::UpsertEntityRecord {
939 collection: collection.to_string(),
940 record,
941 }]
942 })
943 .unwrap_or_default();
944 if registry_dirty {
945 self.mark_paged_registry_dirty();
946 }
947 self.finish_paged_write(actions)?;
948 }
949 Ok(id)
950 }
951
952 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
958 if let Some(entity) = self
960 .get_collection(collection)
961 .and_then(|manager| manager.get(id))
962 {
963 return Some(entity);
964 }
965
966 if self.pager.is_some() {
968 let btree_indices = self.btree_indices.read();
969 if let Some(btree) = btree_indices.get(collection) {
970 let key = id.raw().to_be_bytes();
971 if let Ok(Some(value)) = btree.get(&key) {
972 if let Ok((entity, _)) =
973 Self::deserialize_entity_record(&value, self.format_version())
974 {
975 return Some(entity);
976 }
977 }
978 }
979 }
980
981 None
982 }
983
984 pub fn get_table_row_by_logical_id(
990 &self,
991 collection: &str,
992 logical_id: EntityId,
993 ) -> Option<UnifiedEntity> {
994 if let Some(entity) = self.get(collection, logical_id) {
995 if matches!(entity.kind, EntityKind::TableRow { .. })
996 && entity.logical_id() == logical_id
997 && entity.xmax == 0
998 {
999 return Some(entity);
1000 }
1001 }
1002
1003 let manager = self.get_collection(collection)?;
1004 let mut matches = manager.query_all(|entity| {
1005 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1006 });
1007 matches
1008 .iter()
1009 .find(|entity| entity.xmax == 0)
1010 .cloned()
1011 .or_else(|| matches.pop())
1012 }
1013
1014 pub fn table_row_versions_by_logical_id(
1015 &self,
1016 collection: &str,
1017 logical_id: EntityId,
1018 ) -> Vec<UnifiedEntity> {
1019 self.get_collection(collection)
1020 .map(|manager| {
1021 manager.query_all(|entity| {
1022 matches!(entity.kind, EntityKind::TableRow { .. })
1023 && entity.logical_id() == logical_id
1024 })
1025 })
1026 .unwrap_or_default()
1027 }
1028
1029 pub fn vacuum_mvcc_history(
1030 &self,
1031 collection: &str,
1032 cutoff_xid: u64,
1033 ) -> Result<MvccVacuumStats, StoreError> {
1034 let manager = self
1035 .get_collection(collection)
1036 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1037 let entities =
1038 manager.query_all(|entity| matches!(entity.kind, EntityKind::TableRow { .. }));
1039 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1040 for entity in &entities {
1041 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1042 if entity.xmax == 0 {
1043 entry.0 = true;
1044 }
1045 entry.1 = entry.1.max(entity.xmin);
1046 }
1047
1048 let mut stats = MvccVacuumStats::default();
1049 let mut reclaim_ids = Vec::new();
1050 for entity in entities {
1051 if entity.xmax == 0 {
1052 continue;
1053 }
1054 stats.scanned_versions += 1;
1055 let (has_live_version, max_xmin) = logical
1056 .get(&entity.logical_id())
1057 .copied()
1058 .unwrap_or((false, entity.xmin));
1059 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1060 if entity.xmax < cutoff_xid {
1061 stats.reclaimed_versions += 1;
1062 if is_delete_tombstone {
1063 stats.reclaimed_tombstones += 1;
1064 } else {
1065 stats.reclaimed_history_versions += 1;
1066 }
1067 reclaim_ids.push(entity.id);
1068 } else {
1069 stats.retained_versions += 1;
1070 if is_delete_tombstone {
1071 stats.retained_tombstones += 1;
1072 } else {
1073 stats.retained_history_versions += 1;
1074 }
1075 }
1076 }
1077
1078 if !reclaim_ids.is_empty() {
1079 self.delete_batch(collection, &reclaim_ids)?;
1080 }
1081 Ok(stats)
1082 }
1083
1084 pub(crate) fn install_versioned_table_row_update(
1085 &self,
1086 collection: &str,
1087 old_version: UnifiedEntity,
1088 mut new_version: UnifiedEntity,
1089 metadata: Option<&Metadata>,
1090 ) -> Result<(), StoreError> {
1091 let manager = self
1092 .get_collection(collection)
1093 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1094
1095 let old_id = old_version.id;
1096 let new_id = new_version.id;
1097 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1098
1099 self.entity_cache.remove(old_id.raw());
1100 self.entity_cache.remove(new_id.raw());
1101 manager.update(old_version.clone())?;
1102 self.context_index.remove_entity(old_id);
1103
1104 self.register_entity_id(new_version.id);
1105 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1106 if *row_id == 0 {
1107 *row_id = manager.next_row_id();
1108 } else {
1109 manager.register_row_id(*row_id);
1110 }
1111 }
1112 new_version.ensure_table_logical_id();
1113 manager.insert(new_version.clone())?;
1114 if let Some(metadata) = inherited_metadata {
1115 manager.set_metadata(new_id, metadata)?;
1116 }
1117 self.context_index.index_entity(collection, &new_version);
1118 if self.config.auto_index_refs {
1119 self.index_cross_refs(&new_version, collection)?;
1120 }
1121
1122 let old_metadata = manager.get_metadata(old_id);
1123 let new_metadata = manager.get_metadata(new_id);
1124 let fv = self.format_version();
1125 let records = vec![
1126 Self::serialize_entity_record(&old_version, old_metadata.as_ref(), fv),
1127 Self::serialize_entity_record(&new_version, new_metadata.as_ref(), fv),
1128 ];
1129 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
1130 collection: collection.to_string(),
1131 records,
1132 }])?;
1133
1134 Ok(())
1135 }
1136
1137 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1143 match self.get_collection(collection) {
1144 Some(manager) => manager.get_many(ids),
1145 None => vec![None; ids.len()],
1146 }
1147 }
1148
1149 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1151 if let Some(cached) = self.entity_cache.get(id.raw()) {
1155 return Some(cached);
1156 }
1157
1158 let collections = self.collections.read();
1160 for (name, manager) in collections.iter() {
1161 if let Some(entity) = manager.get(id) {
1162 let result = (name.clone(), entity);
1163 drop(collections);
1167 self.entity_cache.insert(id.raw(), result.clone());
1168 return Some(result);
1169 }
1170 }
1171 None
1172 }
1173
1174 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1180 self.entity_cache.hit_rate()
1181 }
1182
1183 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1185 self.entity_cache.stats()
1186 }
1187
1188 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1190 self.entity_cache.remove(id.raw());
1192 let manager = self
1193 .get_collection(collection)
1194 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1195
1196 let deleted = manager.delete(id)?;
1197 if !deleted {
1198 return Ok(false);
1199 }
1200
1201 let mut registry_dirty = false;
1203 if self.pager.is_some() {
1204 let btree_indices = self.btree_indices.read();
1205 if let Some(btree) = btree_indices.get(collection) {
1206 let root_before = btree.root_page_id();
1207 let key = id.raw().to_be_bytes();
1208 let _ = btree.delete(&key);
1209 registry_dirty = root_before != btree.root_page_id();
1210 }
1211 }
1212
1213 self.unindex_cross_refs(id)?;
1215
1216 self.remove_from_graph_label_index(collection, id);
1218
1219 if registry_dirty {
1220 self.mark_paged_registry_dirty();
1221 }
1222 self.finish_paged_write([StoreWalAction::DeleteEntityRecord {
1223 collection: collection.to_string(),
1224 entity_id: id.raw(),
1225 }])?;
1226
1227 Ok(true)
1228 }
1229
1230 pub fn delete_batch(
1231 &self,
1232 collection: &str,
1233 ids: &[EntityId],
1234 ) -> Result<Vec<EntityId>, StoreError> {
1235 if ids.is_empty() {
1236 return Ok(Vec::new());
1237 }
1238
1239 self.entity_cache.remove_many(ids.iter().map(|id| id.raw()));
1244
1245 let manager = self
1246 .get_collection(collection)
1247 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1248
1249 let deleted_ids = manager.delete_batch(ids)?;
1250 if deleted_ids.is_empty() {
1251 return Ok(deleted_ids);
1252 }
1253
1254 let mut registry_dirty = false;
1255 if self.pager.is_some() {
1256 let btree_indices = self.btree_indices.read();
1257 if let Some(btree) = btree_indices.get(collection) {
1258 let root_before = btree.root_page_id();
1259 for id in &deleted_ids {
1260 let key = id.raw().to_be_bytes();
1261 let _ = btree.delete(&key);
1262 }
1263 registry_dirty = root_before != btree.root_page_id();
1264 }
1265 }
1266
1267 self.unindex_cross_refs_batch(&deleted_ids)?;
1268 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1269 if registry_dirty {
1270 self.mark_paged_registry_dirty();
1271 }
1272 let actions = deleted_ids
1273 .iter()
1274 .map(|id| StoreWalAction::DeleteEntityRecord {
1275 collection: collection.to_string(),
1276 entity_id: id.raw(),
1277 })
1278 .collect::<Vec<_>>();
1279 self.finish_paged_write(actions)?;
1280
1281 Ok(deleted_ids)
1282 }
1283
1284 pub fn set_metadata(
1286 &self,
1287 collection: &str,
1288 id: EntityId,
1289 metadata: Metadata,
1290 ) -> Result<(), StoreError> {
1291 let manager = self
1292 .get_collection(collection)
1293 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1294
1295 manager.set_metadata(id, metadata)?;
1296 if let Some(entity) = manager.get(id) {
1297 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1298 }
1299 Ok(())
1300 }
1301
1302 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1304 self.get_collection(collection)?.get_metadata(id)
1305 }
1306
1307 pub fn add_cross_ref(
1309 &self,
1310 source_collection: &str,
1311 source_id: EntityId,
1312 target_collection: &str,
1313 target_id: EntityId,
1314 ref_type: RefType,
1315 weight: f32,
1316 ) -> Result<(), StoreError> {
1317 let source_manager = self
1319 .get_collection(source_collection)
1320 .ok_or_else(|| StoreError::CollectionNotFound(source_collection.to_string()))?;
1321
1322 if source_manager.get(source_id).is_none() {
1323 return Err(StoreError::EntityNotFound(source_id));
1324 }
1325
1326 let target_manager = self
1328 .get_collection(target_collection)
1329 .ok_or_else(|| StoreError::CollectionNotFound(target_collection.to_string()))?;
1330
1331 if target_manager.get(target_id).is_none() {
1332 return Err(StoreError::EntityNotFound(target_id));
1333 }
1334
1335 let current_refs = self
1337 .cross_refs
1338 .read()
1339 .get(&source_id)
1340 .map_or(0, |v| v.len());
1341
1342 if current_refs >= self.config.max_cross_refs {
1343 return Err(StoreError::TooManyRefs(source_id));
1344 }
1345
1346 let mut registry_dirty = false;
1347 {
1348 let mut forward = self.cross_refs.write();
1349 let refs = forward.entry(source_id).or_default();
1350 let inserted = !refs.iter().any(|(id, kind, coll)| {
1351 *id == target_id && *kind == ref_type && coll == target_collection
1352 });
1353 if inserted {
1354 refs.push((target_id, ref_type, target_collection.to_string()));
1355 registry_dirty = true;
1356 }
1357 }
1358
1359 {
1360 let mut reverse = self.reverse_refs.write();
1361 let refs = reverse.entry(target_id).or_default();
1362 let inserted = !refs.iter().any(|(id, kind, coll)| {
1363 *id == source_id && *kind == ref_type && coll == source_collection
1364 });
1365 if inserted {
1366 refs.push((source_id, ref_type, source_collection.to_string()));
1367 registry_dirty = true;
1368 }
1369 }
1370
1371 if let Some(mut entity) = source_manager.get(source_id) {
1372 if !entity.cross_refs().iter().any(|xref| {
1373 xref.target == target_id
1374 && xref.ref_type == ref_type
1375 && xref.target_collection == target_collection
1376 }) {
1377 let cross_ref = CrossRef::with_weight(
1378 source_id,
1379 target_id,
1380 target_collection,
1381 ref_type,
1382 weight,
1383 );
1384 entity.add_cross_ref(cross_ref);
1385 let _ = source_manager.update(entity.clone());
1386 registry_dirty = true;
1387 self.persist_entities_to_pager(source_collection, std::slice::from_ref(&entity))?;
1388 }
1389 }
1390
1391 if registry_dirty {
1392 self.mark_paged_registry_dirty();
1393 if matches!(
1394 self.config.durability_mode,
1395 crate::api::DurabilityMode::Strict
1396 ) {
1397 self.flush_paged_state()?;
1398 }
1399 }
1400
1401 Ok(())
1402 }
1403
1404 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1406 self.cross_refs.read().get(&id).cloned().unwrap_or_default()
1407 }
1408
1409 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1411 self.reverse_refs
1412 .read()
1413 .get(&id)
1414 .cloned()
1415 .unwrap_or_default()
1416 }
1417
1418 pub fn expand_refs(
1420 &self,
1421 id: EntityId,
1422 depth: u32,
1423 ref_types: Option<&[RefType]>,
1424 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1425 let mut results = Vec::new();
1426 let mut visited = std::collections::HashSet::new();
1427 visited.insert(id);
1428
1429 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1430
1431 results
1432 }
1433
1434 fn expand_refs_recursive(
1435 &self,
1436 id: EntityId,
1437 max_depth: u32,
1438 ref_types: Option<&[RefType]>,
1439 visited: &mut std::collections::HashSet<EntityId>,
1440 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1441 current_depth: u32,
1442 ) {
1443 if current_depth > max_depth {
1444 return;
1445 }
1446
1447 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1448 if visited.contains(&target_id) {
1449 continue;
1450 }
1451
1452 if let Some(types) = ref_types {
1453 if !types.contains(&ref_type) {
1454 continue;
1455 }
1456 }
1457
1458 visited.insert(target_id);
1459
1460 if let Some(entity) = self.get(&target_collection, target_id) {
1461 results.push((entity, current_depth, ref_type));
1462
1463 self.expand_refs_recursive(
1465 target_id,
1466 max_depth,
1467 ref_types,
1468 visited,
1469 results,
1470 current_depth + 1,
1471 );
1472 }
1473 }
1474 }
1475
1476 pub(crate) fn index_cross_refs(
1478 &self,
1479 entity: &UnifiedEntity,
1480 collection: &str,
1481 ) -> Result<(), StoreError> {
1482 let mut registry_dirty = false;
1483 for cross_ref in entity.cross_refs() {
1484 if cross_ref.target_collection.is_empty() {
1485 continue;
1486 }
1487 {
1488 let mut forward = self.cross_refs.write();
1489 let refs = forward.entry(cross_ref.source).or_default();
1490 let inserted = !refs.iter().any(|(id, kind, coll)| {
1491 *id == cross_ref.target
1492 && *kind == cross_ref.ref_type
1493 && coll == &cross_ref.target_collection
1494 });
1495 if inserted {
1496 refs.push((
1497 cross_ref.target,
1498 cross_ref.ref_type,
1499 cross_ref.target_collection.clone(),
1500 ));
1501 registry_dirty = true;
1502 }
1503 }
1504
1505 {
1506 let mut reverse = self.reverse_refs.write();
1507 let refs = reverse.entry(cross_ref.target).or_default();
1508 let inserted = !refs.iter().any(|(id, kind, coll)| {
1509 *id == cross_ref.source && *kind == cross_ref.ref_type && coll == collection
1510 });
1511 if inserted {
1512 refs.push((cross_ref.source, cross_ref.ref_type, collection.to_string()));
1513 registry_dirty = true;
1514 }
1515 }
1516 }
1517
1518 if registry_dirty {
1519 self.mark_paged_registry_dirty();
1520 }
1521
1522 Ok(())
1523 }
1524
1525 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1527 self.cross_refs.write().remove(&id);
1529
1530 let mut reverse = self.reverse_refs.write();
1532 for refs in reverse.values_mut() {
1533 refs.retain(|(source, _, _)| *source != id);
1534 }
1535 reverse.remove(&id);
1536 self.mark_paged_registry_dirty();
1537
1538 Ok(())
1539 }
1540
1541 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1542 if ids.is_empty() {
1543 return Ok(());
1544 }
1545
1546 let id_set: std::collections::HashSet<EntityId> = ids.iter().copied().collect();
1547
1548 let needs_forward_cleanup = {
1562 let forward = self.cross_refs.read();
1563 id_set.iter().any(|id| forward.contains_key(id))
1564 };
1565 let needs_reverse_cleanup = {
1566 let reverse = self.reverse_refs.read();
1567 id_set.iter().any(|id| reverse.contains_key(id))
1568 };
1569
1570 if !needs_forward_cleanup && !needs_reverse_cleanup {
1571 self.unindex_cross_refs_fast_path
1572 .fetch_add(1, Ordering::Relaxed);
1573 return Ok(());
1574 }
1575
1576 if needs_forward_cleanup {
1577 let mut forward = self.cross_refs.write();
1578 for id in &id_set {
1579 forward.remove(id);
1580 }
1581 }
1582
1583 if needs_reverse_cleanup || needs_forward_cleanup {
1584 let mut reverse = self.reverse_refs.write();
1588 if needs_forward_cleanup {
1589 for refs in reverse.values_mut() {
1590 refs.retain(|(source, _, _)| !id_set.contains(source));
1591 }
1592 }
1593 reverse.retain(|target, refs| !id_set.contains(target) && !refs.is_empty());
1594 }
1595 self.mark_paged_registry_dirty();
1596
1597 Ok(())
1598 }
1599
1600 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1604 self.unindex_cross_refs_fast_path.load(Ordering::Relaxed)
1605 }
1606
1607 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1609 where
1610 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1611 {
1612 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1623 let collections = self.collections.read();
1624 collections
1625 .iter()
1626 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1627 .collect()
1628 };
1629
1630 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1631 if !use_parallel {
1632 return pairs
1634 .into_iter()
1635 .flat_map(|(name, mgr)| {
1636 mgr.query_all(filter.clone())
1637 .into_iter()
1638 .map(move |e| (name.clone(), e))
1639 })
1640 .collect();
1641 }
1642
1643 let filter_ref = &filter;
1645 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|s| {
1646 pairs
1647 .iter()
1648 .map(|(name, manager)| {
1649 let name = (*name).clone();
1650 s.spawn(move || {
1651 manager
1652 .query_all(|e| filter_ref(e))
1653 .into_iter()
1654 .map(|e| (name.clone(), e))
1655 .collect::<Vec<_>>()
1656 })
1657 })
1658 .collect::<Vec<_>>()
1659 .into_iter()
1660 .map(|h| h.join().unwrap_or_default())
1661 .collect()
1662 });
1663
1664 collection_results.into_iter().flatten().collect()
1665 }
1666
1667 pub fn scan<F>(
1669 &self,
1670 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1671 filter: F,
1672 ) -> Vec<(String, UnifiedEntity)>
1673 where
1674 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1675 {
1676 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1677 let collections = self.collections.read();
1678 collections
1679 .iter()
1680 .map(|(name, manager)| (name.clone(), Arc::clone(manager)))
1681 .collect()
1682 };
1683
1684 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1685 if !use_parallel {
1686 return pairs
1687 .into_iter()
1688 .flat_map(|(name, manager)| {
1689 manager
1690 .scan(snapshot, filter.clone())
1691 .into_iter()
1692 .map(move |entity| (name.clone(), entity))
1693 })
1694 .collect();
1695 }
1696
1697 let filter_ref = &filter;
1698 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|scope| {
1699 pairs
1700 .iter()
1701 .map(|(name, manager)| {
1702 let name = name.clone();
1703 scope.spawn(move || {
1704 manager
1705 .scan(snapshot, |entity| filter_ref(entity))
1706 .into_iter()
1707 .map(|entity| (name.clone(), entity))
1708 .collect::<Vec<_>>()
1709 })
1710 })
1711 .collect::<Vec<_>>()
1712 .into_iter()
1713 .map(|handle| handle.join().unwrap_or_default())
1714 .collect()
1715 });
1716
1717 collection_results.into_iter().flatten().collect()
1718 }
1719
1720 pub fn filter_metadata_all(
1722 &self,
1723 filters: &[(String, MetadataFilter)],
1724 ) -> Vec<(String, EntityId)> {
1725 let mut results = Vec::new();
1726 let collections = self.collections.read();
1727
1728 for (name, manager) in collections.iter() {
1729 for id in manager.filter_metadata(filters) {
1730 results.push((name.clone(), id));
1731 }
1732 }
1733
1734 results
1735 }
1736
1737 pub fn stats(&self) -> StoreStats {
1739 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1745 let collections = self.collections.read();
1746 collections
1747 .iter()
1748 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1749 .collect()
1750 };
1751
1752 let mut stats = StoreStats {
1753 collection_count: pairs.len(),
1754 ..Default::default()
1755 };
1756
1757 for (name, manager) in &pairs {
1758 let manager_stats = manager.stats();
1759 stats.total_entities += manager_stats.total_entities;
1760 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1761 stats.collections.insert(name.clone(), manager_stats);
1762 }
1763
1764 stats
1765 }
1766
1767 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1769 let collections = self.collections.read();
1770 for manager in collections.values() {
1771 manager.run_maintenance()?;
1772 }
1773 Ok(())
1774 }
1775
1776 pub fn reclaimable_segment_bytes(&self) -> u64 {
1778 let collections = self.collections.read();
1779 collections
1780 .values()
1781 .map(|manager| manager.reclaimable_bytes())
1782 .fold(0, u64::saturating_add)
1783 }
1784
1785 pub fn pressure_consolidation_tick(&self) -> bool {
1788 let managers: Vec<_> = self.collections.read().values().cloned().collect();
1789 let mut advanced = false;
1792 for manager in &managers {
1793 advanced |= manager.pressure_consolidation_tick();
1794 }
1795 advanced
1796 }
1797}
1798
1799fn flatten_config_json(
1801 prefix: &str,
1802 value: &crate::serde_json::Value,
1803 out: &mut Vec<(String, reddb_types::Value)>,
1804) {
1805 use reddb_types::Value;
1806 match value {
1807 crate::serde_json::Value::Object(map) => {
1808 for (k, v) in map {
1809 let key = if prefix.is_empty() {
1810 k.clone()
1811 } else {
1812 format!("{prefix}.{k}")
1813 };
1814 flatten_config_json(&key, v, out);
1815 }
1816 }
1817 crate::serde_json::Value::String(s) => {
1818 out.push((prefix.to_string(), Value::text(s.clone())));
1819 }
1820 crate::serde_json::Value::Integer(n) => {
1821 out.push((prefix.to_string(), Value::Integer(*n)));
1822 }
1823 crate::serde_json::Value::Number(n) => {
1824 if n.fract().abs() < f64::EPSILON {
1825 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1826 } else {
1827 out.push((prefix.to_string(), Value::Float(*n)));
1828 }
1829 }
1830 crate::serde_json::Value::Decimal(n) => {
1831 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1832 }
1833 crate::serde_json::Value::Bool(b) => {
1834 out.push((prefix.to_string(), Value::Boolean(*b)));
1835 }
1836 crate::serde_json::Value::Null => {
1837 out.push((prefix.to_string(), Value::Null));
1838 }
1839 crate::serde_json::Value::Array(arr) => {
1840 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1841 out.push((prefix.to_string(), Value::text(json_str)));
1842 }
1843 }
1844}