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 && !Self::deferred_store_wal_capture_active() {
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 && !Self::deferred_store_wal_capture_active() {
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 _vault_guard = self.vault_publication_lock.lock();
477 let drop = self.prepare_operational_collection_drop(name)?;
478 let manager = {
479 let mut collections = self.collections.write();
480
481 collections
482 .remove(name)
483 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
484 };
485
486 let entities = manager.query_all(|_| true);
487 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
488
489 for entity_id in &entity_ids {
490 self.context_index.remove_entity(*entity_id);
491 let _ = self.unindex_cross_refs(*entity_id);
492 }
493
494 if let Some(tree) = self.btree_indices.write().remove(name) {
495 self.retire_btree(tree);
496 }
497
498 self.entity_cache.retain(|entity_id, (collection, _)| {
499 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
500 });
501
502 self.cross_refs.write().retain(|source_id, refs| {
503 refs.retain(|(target_id, _, target_collection)| {
504 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
505 });
506 !entity_ids.iter().any(|id| id == source_id)
507 });
508
509 self.reverse_refs.write().retain(|target_id, refs| {
510 refs.retain(|(source_id, _, source_collection)| {
511 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
512 });
513 !entity_ids.iter().any(|id| id == target_id)
514 });
515
516 self.mark_paged_registry_dirty();
517 self.finish_paged_write([StoreWalAction::DropCollection {
518 name: name.to_string(),
519 }])?;
520 if let Some(drop) = drop {
521 drop.finish().map_err(StoreError::Io)?;
522 }
523
524 Ok(())
525 }
526
527 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
529 let manager = self
530 .get_collection(collection)
531 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
532
533 let mut entity = entity;
534 if entity.id.raw() == 0 {
535 entity.id = self.next_entity_id();
536 } else {
537 self.register_entity_id(entity.id);
538 }
539 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
541 if *row_id == 0 {
542 *row_id = manager.next_row_id();
543 } else {
544 manager.register_row_id(*row_id);
545 }
546 }
547 entity.ensure_table_logical_id();
548 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
550 {
551 Some(node.label.clone())
552 } else {
553 None
554 };
555
556 let id = manager.insert(entity)?;
557 self.register_entity_id(id);
558
559 if let Some(ref label) = graph_node_label {
561 self.update_graph_label_index(collection, label, id);
562 }
563
564 let mut registry_dirty = false;
567 if let Some(pager) = self
568 .pager
569 .as_ref()
570 .filter(|_| !Self::deferred_store_wal_capture_active())
571 {
572 if let Some(entity) = manager.get(id) {
573 let mut btree_indices = self.btree_indices.write();
574 let btree = btree_indices
575 .entry(collection.to_string())
576 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
577 let root_before = btree.root_page_id();
578
579 let key = id.raw().to_be_bytes();
580 let metadata = manager.get_metadata(id);
581 let value = Self::serialize_entity_record(
582 &entity,
583 metadata.as_ref(),
584 self.format_version(),
585 );
586 let _ = btree.insert(&key, &value);
588 registry_dirty = root_before != btree.root_page_id();
589 }
590 }
591
592 if self.config.auto_index_refs {
594 if let Some(entity) = manager.get(id) {
595 self.index_cross_refs(&entity, collection)?;
596 }
597 }
598
599 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
603 let actions = manager
604 .get(id)
605 .map(|entity| {
606 let metadata = manager.get_metadata(id);
607 vec![StoreWalAction::upsert_entity(
608 collection,
609 &entity,
610 metadata.as_ref(),
611 self.format_version(),
612 )]
613 })
614 .unwrap_or_default();
615 if registry_dirty {
616 self.mark_paged_registry_dirty();
617 }
618 self.finish_paged_write(actions)?;
619 }
620
621 Ok(id)
622 }
623
624 pub fn bulk_insert(
629 &self,
630 collection: &str,
631 mut entities: Vec<UnifiedEntity>,
632 ) -> Result<Vec<EntityId>, StoreError> {
633 let _publication = self.vault_publication_lock.lock();
636 let trace = matches!(
640 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
641 Some("1") | Some("true") | Some("on")
642 );
643 let t_start = std::time::Instant::now();
644 let n = entities.len();
645 let manager = self.get_or_create_collection(collection);
646 let t_get_coll = t_start.elapsed();
647
648 let t0 = std::time::Instant::now();
657 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
658 let n_missing_row_ids = entities
659 .iter()
660 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
661 .count() as u64;
662 let mut entity_id_range = if n_missing_entity_ids > 0 {
663 self.reserve_entity_ids(n_missing_entity_ids)
664 } else {
665 0..0
666 };
667 let mut row_id_range = if n_missing_row_ids > 0 {
668 manager.reserve_row_ids(n_missing_row_ids)
669 } else {
670 0..0
671 };
672 for entity in &mut entities {
673 if entity.id.raw() == 0 {
674 let next = entity_id_range
675 .next()
676 .expect("reserved entity-id range exhausted");
677 entity.id = EntityId::new(next);
678 } else {
679 self.register_entity_id(entity.id);
680 }
681 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
682 if *row_id == 0 {
683 *row_id = row_id_range
684 .next()
685 .expect("reserved row-id range exhausted");
686 } else {
687 manager.register_row_id(*row_id);
688 }
689 }
690 entity.ensure_table_logical_id();
691 }
692 let t_assign_ids = t0.elapsed();
693
694 let graph_labels: Vec<Option<(String, EntityId)>> = entities
696 .iter()
697 .map(|e| {
698 if let EntityKind::GraphNode(ref node) = e.kind {
699 Some((node.label.clone(), e.id))
700 } else {
701 None
702 }
703 })
704 .collect();
705
706 let t0 = std::time::Instant::now();
713 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
714 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
715 let fv = self.format_version();
716 let serial_map = |e: &UnifiedEntity| {
717 (
718 e.id.raw().to_be_bytes().to_vec(),
719 Self::serialize_entity_record(e, None, fv),
720 )
721 };
722 if entities.len() >= 512 {
728 use rayon::prelude::*;
729 Some(entities.par_iter().map(serial_map).collect())
730 } else {
731 Some(entities.iter().map(serial_map).collect())
732 }
733 } else {
734 None
735 };
736 let t_serialize = t0.elapsed();
737
738 let t0 = std::time::Instant::now();
740 let ids = manager.bulk_insert(entities)?;
741 let t_manager = t0.elapsed();
742 for id in &ids {
743 self.register_entity_id(*id);
744 }
745
746 for (label, entity_id) in graph_labels.iter().flatten() {
748 self.update_graph_label_index(collection, label, *entity_id);
749 }
750
751 let skip_btree_requested = matches!(
761 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
762 .ok()
763 .as_deref(),
764 Some("1") | Some("true") | Some("on")
765 );
766 let skip_btree = skip_btree_requested && self.pager.is_none();
769 if skip_btree_requested && !skip_btree {
770 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
772 IGNORED.get_or_init(|| {
773 tracing::warn!(
774 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
775 active — flag ignored; bulk inserts will be persisted normally"
776 );
777 });
778 } else if skip_btree {
779 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
781 WARNED.get_or_init(|| {
782 tracing::warn!(
783 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
784 bulk inserts NOT durable; data will be lost on restart"
785 );
786 });
787 }
788
789 let mut t_btree_lock = std::time::Duration::ZERO;
793 let mut t_btree_insert = std::time::Duration::ZERO;
794 let mut t_flush = std::time::Duration::ZERO;
795 if !skip_btree && !Self::deferred_store_wal_capture_active() {
796 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
797 let t0 = std::time::Instant::now();
798 let mut btree_indices = self.btree_indices.write();
799 let btree = btree_indices
800 .entry(collection.to_string())
801 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
802 let root_before = btree.root_page_id();
803 t_btree_lock = t0.elapsed();
804
805 let t0 = std::time::Instant::now();
806 let _ = btree.bulk_insert_sorted(batch);
807 t_btree_insert = t0.elapsed();
808 let registry_dirty = root_before != btree.root_page_id();
809
810 let t0 = std::time::Instant::now();
811 if registry_dirty {
812 self.mark_paged_registry_dirty();
813 }
814 t_flush = t0.elapsed();
815 }
816 }
817
818 let actions = serialized
824 .map(|batch| {
825 let records: Vec<Vec<u8>> =
826 batch.into_iter().map(|(_key, record)| record).collect();
827 vec![StoreWalAction::BulkUpsertEntityRecords {
828 collection: collection.to_string(),
829 records,
830 }]
831 })
832 .unwrap_or_default();
833 self.finish_paged_write(actions)?;
834
835 if trace {
836 tracing::debug!(
837 n,
838 total = ?t_start.elapsed(),
839 get_coll = ?t_get_coll,
840 assign = ?t_assign_ids,
841 serialize = ?t_serialize,
842 manager = ?t_manager,
843 btree_lock = ?t_btree_lock,
844 btree = ?t_btree_insert,
845 flush = ?t_flush,
846 "bulk_insert timing"
847 );
848 }
849
850 Ok(ids)
851 }
852
853 pub fn insert_auto(
855 &self,
856 collection: &str,
857 entity: UnifiedEntity,
858 ) -> Result<EntityId, StoreError> {
859 let _publication = self.vault_publication_lock.lock();
861 let manager = self.get_or_create_collection(collection);
862 let mut entity = entity;
863 if entity.id.raw() == 0 {
864 entity.id = self.next_entity_id();
865 } else {
866 self.register_entity_id(entity.id);
867 }
868 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
870 if *row_id == 0 {
871 *row_id = manager.next_row_id();
872 } else {
873 manager.register_row_id(*row_id);
874 }
875 }
876 entity.ensure_table_logical_id();
877
878 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
880 {
881 Some(node.label.clone())
882 } else {
883 None
884 };
885 self.context_index.index_entity(collection, &entity);
887
888 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
891 if self.config.auto_index_refs {
892 self.index_cross_refs(&entity, collection)?;
893 }
894
895 let paged_btree = if persist_record && !Self::deferred_store_wal_capture_active() {
902 self.get_or_create_btree(collection)
903 } else {
904 None
905 };
906 let persist_fence = paged_btree
907 .as_ref()
908 .map(|_| self.btree_indices.read_recursive());
909 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
910 let unversioned = entity.xmin == 0 && entity.xmax == 0;
911
912 let id = manager.insert(entity)?;
913 let serialized_record = if persist_record {
914 Some(
915 manager
916 .get_with(id, |entity| {
917 Self::serialize_entity_record(entity, None, self.format_version())
918 })
919 .ok_or(StoreError::EntityNotFound(id))?,
920 )
921 } else {
922 None
923 };
924 if let Some(ref label) = graph_node_label {
932 self.update_graph_label_index(collection, label, id);
933 }
934
935 let mut registry_dirty = false;
936 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
937 let root_before = btree.root_page_id();
938
939 let key = id.raw().to_be_bytes();
940 btree.insert(&key, record).map_err(|e| {
941 StoreError::Io(std::io::Error::other(format!(
942 "B-tree insert error while inserting '{collection}'/{id}: {e}"
943 )))
944 })?;
945 registry_dirty = root_before != btree.root_page_id();
946 }
947 drop(persist_fence);
948
949 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
953 let actions = serialized_record
954 .map(|record| {
955 vec![StoreWalAction::UpsertEntityRecord {
956 collection: collection.to_string(),
957 record,
958 }]
959 })
960 .unwrap_or_default();
961 if registry_dirty {
962 self.mark_paged_registry_dirty();
963 }
964 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
965 drop(_publication);
966 self.complete_grouped_actions(&pending, &actions)?;
967 } else {
968 self.finish_paged_write(actions)?;
969 }
970 }
971 if unversioned && !Self::deferred_store_wal_capture_active() {
972 if let Some(before) = before {
973 self.note_paged_insert(collection, &manager, &before, id);
974 }
975 }
976 Ok(id)
977 }
978
979 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
985 if let Some(entity) = self
987 .get_collection(collection)
988 .and_then(|manager| manager.get(id))
989 {
990 return Some(entity);
991 }
992
993 if self.pager.is_some() {
995 let btree_indices = self.btree_indices.read();
996 if let Some(btree) = btree_indices.get(collection) {
997 let key = id.raw().to_be_bytes();
998 if let Ok(Some(value)) = btree.get(&key) {
999 if let Ok((entity, _)) =
1000 Self::deserialize_entity_record(&value, self.format_version())
1001 {
1002 return Some(entity);
1003 }
1004 }
1005 }
1006 }
1007
1008 None
1009 }
1010
1011 pub fn get_table_row_by_logical_id(
1017 &self,
1018 collection: &str,
1019 logical_id: EntityId,
1020 ) -> Option<UnifiedEntity> {
1021 if let Some(entity) = self.get(collection, logical_id) {
1022 if matches!(entity.kind, EntityKind::TableRow { .. })
1023 && entity.logical_id() == logical_id
1024 && entity.xmax == 0
1025 {
1026 return Some(entity);
1027 }
1028 }
1029
1030 let manager = self.get_collection(collection)?;
1031 let mut matches = manager.query_all(|entity| {
1032 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1033 });
1034 matches
1035 .iter()
1036 .find(|entity| entity.xmax == 0)
1037 .cloned()
1038 .or_else(|| matches.pop())
1039 }
1040
1041 pub fn table_row_versions_by_logical_id(
1042 &self,
1043 collection: &str,
1044 logical_id: EntityId,
1045 ) -> Vec<UnifiedEntity> {
1046 self.get_collection(collection)
1047 .map(|manager| {
1048 manager.query_all(|entity| {
1049 matches!(entity.kind, EntityKind::TableRow { .. })
1050 && entity.logical_id() == logical_id
1051 })
1052 })
1053 .unwrap_or_default()
1054 }
1055
1056 pub fn vacuum_mvcc_history(
1057 &self,
1058 collection: &str,
1059 cutoff_xid: u64,
1060 ) -> Result<MvccVacuumStats, StoreError> {
1061 let manager = self
1062 .get_collection(collection)
1063 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1064 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1065 manager.for_each_entity(|entity| {
1066 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1067 return true;
1068 }
1069 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1070 if entity.xmax == 0 {
1071 entry.0 = true;
1072 }
1073 entry.1 = entry.1.max(entity.xmin);
1074 true
1075 });
1076
1077 let mut stats = MvccVacuumStats::default();
1078 let mut reclaim_ids = Vec::new();
1079 manager.for_each_entity(|entity| {
1080 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1081 return true;
1082 }
1083 stats.scanned_versions += 1;
1084 let (has_live_version, max_xmin) = logical
1085 .get(&entity.logical_id())
1086 .copied()
1087 .unwrap_or((false, entity.xmin));
1088 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1089 if entity.xmax < cutoff_xid {
1090 stats.reclaimed_versions += 1;
1091 if is_delete_tombstone {
1092 stats.reclaimed_tombstones += 1;
1093 } else {
1094 stats.reclaimed_history_versions += 1;
1095 }
1096 reclaim_ids.push(entity.id);
1097 } else {
1098 stats.retained_versions += 1;
1099 if is_delete_tombstone {
1100 stats.retained_tombstones += 1;
1101 } else {
1102 stats.retained_history_versions += 1;
1103 }
1104 }
1105 true
1106 });
1107
1108 if !reclaim_ids.is_empty() {
1109 self.delete_batch(collection, &reclaim_ids)?;
1110 }
1111 Ok(stats)
1112 }
1113
1114 pub(crate) fn install_versioned_table_row_update(
1115 &self,
1116 collection: &str,
1117 old_version: UnifiedEntity,
1118 mut new_version: UnifiedEntity,
1119 metadata: Option<&Metadata>,
1120 ) -> Result<(), StoreError> {
1121 let manager = self
1122 .get_collection(collection)
1123 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1124
1125 let old_id = old_version.id;
1126 let new_id = new_version.id;
1127 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1128
1129 self.entity_cache.remove(old_id.raw());
1130 self.entity_cache.remove(new_id.raw());
1131 manager.update(old_version.clone())?;
1132 self.context_index.remove_entity(old_id);
1133
1134 self.register_entity_id(new_version.id);
1135 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1136 if *row_id == 0 {
1137 *row_id = manager.next_row_id();
1138 } else {
1139 manager.register_row_id(*row_id);
1140 }
1141 }
1142 new_version.ensure_table_logical_id();
1143 manager.insert(new_version.clone())?;
1144 if let Some(metadata) = inherited_metadata {
1145 manager.set_metadata(new_id, metadata)?;
1146 }
1147 self.context_index.index_entity(collection, &new_version);
1148 if self.config.auto_index_refs {
1149 self.index_cross_refs(&new_version, collection)?;
1150 }
1151
1152 let old_metadata = manager.get_metadata(old_id);
1153 let new_metadata = manager.get_metadata(new_id);
1154 let fv = self.format_version();
1155 let records = vec![
1156 Self::serialize_entity_record(&old_version, old_metadata.as_ref(), fv),
1157 Self::serialize_entity_record(&new_version, new_metadata.as_ref(), fv),
1158 ];
1159 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
1160 collection: collection.to_string(),
1161 records,
1162 }])?;
1163
1164 Ok(())
1165 }
1166
1167 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1173 match self.get_collection(collection) {
1174 Some(manager) => manager.get_many(ids),
1175 None => vec![None; ids.len()],
1176 }
1177 }
1178
1179 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1181 if let Some(cached) = self.entity_cache.get(id.raw()) {
1185 return Some(cached);
1186 }
1187
1188 let collections = self.collections.read();
1190 for (name, manager) in collections.iter() {
1191 if let Some(entity) = manager.get(id) {
1192 let result = (name.clone(), entity);
1193 drop(collections);
1197 self.entity_cache.insert(id.raw(), result.clone());
1198 return Some(result);
1199 }
1200 }
1201 None
1202 }
1203
1204 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1210 self.entity_cache.hit_rate()
1211 }
1212
1213 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1215 self.entity_cache.stats()
1216 }
1217
1218 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1220 let publication = self.vault_publication_lock.lock();
1221 self.entity_cache.remove(id.raw());
1223 let manager = self
1224 .get_collection(collection)
1225 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1226
1227 let deleted = manager.delete(id)?;
1228 if !deleted {
1229 return Ok(false);
1230 }
1231
1232 let mut registry_dirty = false;
1234 if self.pager.is_some() {
1235 let btree_indices = self.btree_indices.read();
1236 if let Some(btree) = btree_indices.get(collection) {
1237 let root_before = btree.root_page_id();
1238 let key = id.raw().to_be_bytes();
1239 let _ = btree.delete(&key);
1240 registry_dirty = root_before != btree.root_page_id();
1241 }
1242 }
1243
1244 self.unindex_cross_refs(id)?;
1246
1247 self.remove_from_graph_label_index(collection, id);
1249
1250 if registry_dirty {
1251 self.mark_paged_registry_dirty();
1252 }
1253 let actions = [StoreWalAction::DeleteEntityRecord {
1254 collection: collection.to_string(),
1255 entity_id: id.raw(),
1256 }];
1257 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1258 drop(publication);
1259 self.complete_grouped_actions(&pending, &actions)?;
1260 } else {
1261 self.finish_paged_write(actions)?;
1262 }
1263
1264 Ok(true)
1265 }
1266
1267 pub fn delete_batch(
1268 &self,
1269 collection: &str,
1270 ids: &[EntityId],
1271 ) -> Result<Vec<EntityId>, StoreError> {
1272 if ids.is_empty() {
1273 return Ok(Vec::new());
1274 }
1275 let _publication = self.vault_publication_lock.lock();
1278
1279 self.entity_cache.remove_many(ids.iter().map(|id| id.raw()));
1284
1285 let manager = self
1286 .get_collection(collection)
1287 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1288
1289 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1290 let deleted_ids = manager.delete_batch(ids)?;
1291 if deleted_ids.is_empty() {
1292 return Ok(deleted_ids);
1293 }
1294
1295 let mut registry_dirty = false;
1296 let mut updated_tree = false;
1297 if self.pager.is_some() {
1298 let btree_indices = self.btree_indices.read();
1299 if let Some(btree) = btree_indices.get(collection) {
1300 updated_tree = true;
1301 let root_before = btree.root_page_id();
1302 for id in &deleted_ids {
1303 let key = id.raw().to_be_bytes();
1304 updated_tree &= matches!(btree.delete(&key), Ok(true));
1305 }
1306 registry_dirty = root_before != btree.root_page_id();
1307 }
1308 }
1309
1310 self.unindex_cross_refs_batch(&deleted_ids)?;
1311 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1312 if registry_dirty {
1313 self.mark_paged_registry_dirty();
1314 }
1315 let actions = deleted_ids
1316 .iter()
1317 .map(|id| StoreWalAction::DeleteEntityRecord {
1318 collection: collection.to_string(),
1319 entity_id: id.raw(),
1320 })
1321 .collect::<Vec<_>>();
1322 self.finish_paged_write(actions)?;
1323
1324 if updated_tree {
1325 if let Some(before) = before {
1326 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1327 }
1328 self.flush_paged_registry()?;
1333 }
1334 Ok(deleted_ids)
1335 }
1336
1337 pub fn set_metadata(
1339 &self,
1340 collection: &str,
1341 id: EntityId,
1342 metadata: Metadata,
1343 ) -> Result<(), StoreError> {
1344 let manager = self
1345 .get_collection(collection)
1346 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1347
1348 manager.set_metadata(id, metadata)?;
1349 if let Some(entity) = manager.get(id) {
1350 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1351 }
1352 Ok(())
1353 }
1354
1355 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1357 self.get_collection(collection)?.get_metadata(id)
1358 }
1359
1360 pub fn add_cross_ref(
1362 &self,
1363 source_collection: &str,
1364 source_id: EntityId,
1365 target_collection: &str,
1366 target_id: EntityId,
1367 ref_type: RefType,
1368 weight: f32,
1369 ) -> Result<(), StoreError> {
1370 let source_manager = self
1372 .get_collection(source_collection)
1373 .ok_or_else(|| StoreError::CollectionNotFound(source_collection.to_string()))?;
1374
1375 if source_manager.get(source_id).is_none() {
1376 return Err(StoreError::EntityNotFound(source_id));
1377 }
1378
1379 let target_manager = self
1381 .get_collection(target_collection)
1382 .ok_or_else(|| StoreError::CollectionNotFound(target_collection.to_string()))?;
1383
1384 if target_manager.get(target_id).is_none() {
1385 return Err(StoreError::EntityNotFound(target_id));
1386 }
1387
1388 let current_refs = self
1390 .cross_refs
1391 .read()
1392 .get(&source_id)
1393 .map_or(0, |v| v.len());
1394
1395 if current_refs >= self.config.max_cross_refs {
1396 return Err(StoreError::TooManyRefs(source_id));
1397 }
1398
1399 let mut registry_dirty = false;
1400 {
1401 let mut forward = self.cross_refs.write();
1402 let refs = forward.entry(source_id).or_default();
1403 let inserted = !refs.iter().any(|(id, kind, coll)| {
1404 *id == target_id && *kind == ref_type && coll == target_collection
1405 });
1406 if inserted {
1407 refs.push((target_id, ref_type, target_collection.to_string()));
1408 registry_dirty = true;
1409 }
1410 }
1411
1412 {
1413 let mut reverse = self.reverse_refs.write();
1414 let refs = reverse.entry(target_id).or_default();
1415 let inserted = !refs.iter().any(|(id, kind, coll)| {
1416 *id == source_id && *kind == ref_type && coll == source_collection
1417 });
1418 if inserted {
1419 refs.push((source_id, ref_type, source_collection.to_string()));
1420 registry_dirty = true;
1421 }
1422 }
1423
1424 if let Some(mut entity) = source_manager.get(source_id) {
1425 if !entity.cross_refs().iter().any(|xref| {
1426 xref.target == target_id
1427 && xref.ref_type == ref_type
1428 && xref.target_collection == target_collection
1429 }) {
1430 let cross_ref = CrossRef::with_weight(
1431 source_id,
1432 target_id,
1433 target_collection,
1434 ref_type,
1435 weight,
1436 );
1437 entity.add_cross_ref(cross_ref);
1438 let _ = source_manager.update(entity.clone());
1439 registry_dirty = true;
1440 self.persist_entities_to_pager(source_collection, std::slice::from_ref(&entity))?;
1441 }
1442 }
1443
1444 if registry_dirty {
1445 self.mark_paged_registry_dirty();
1446 if matches!(
1447 self.config.durability_mode,
1448 crate::api::DurabilityMode::Strict
1449 ) {
1450 self.flush_paged_state()?;
1451 }
1452 }
1453
1454 Ok(())
1455 }
1456
1457 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1459 self.cross_refs.read().get(&id).cloned().unwrap_or_default()
1460 }
1461
1462 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1464 self.reverse_refs
1465 .read()
1466 .get(&id)
1467 .cloned()
1468 .unwrap_or_default()
1469 }
1470
1471 pub fn expand_refs(
1473 &self,
1474 id: EntityId,
1475 depth: u32,
1476 ref_types: Option<&[RefType]>,
1477 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1478 let mut results = Vec::new();
1479 let mut visited = std::collections::HashSet::new();
1480 visited.insert(id);
1481
1482 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1483
1484 results
1485 }
1486
1487 fn expand_refs_recursive(
1488 &self,
1489 id: EntityId,
1490 max_depth: u32,
1491 ref_types: Option<&[RefType]>,
1492 visited: &mut std::collections::HashSet<EntityId>,
1493 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1494 current_depth: u32,
1495 ) {
1496 if current_depth > max_depth {
1497 return;
1498 }
1499
1500 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1501 if visited.contains(&target_id) {
1502 continue;
1503 }
1504
1505 if let Some(types) = ref_types {
1506 if !types.contains(&ref_type) {
1507 continue;
1508 }
1509 }
1510
1511 visited.insert(target_id);
1512
1513 if let Some(entity) = self.get(&target_collection, target_id) {
1514 results.push((entity, current_depth, ref_type));
1515
1516 self.expand_refs_recursive(
1518 target_id,
1519 max_depth,
1520 ref_types,
1521 visited,
1522 results,
1523 current_depth + 1,
1524 );
1525 }
1526 }
1527 }
1528
1529 pub(crate) fn index_cross_refs(
1531 &self,
1532 entity: &UnifiedEntity,
1533 collection: &str,
1534 ) -> Result<(), StoreError> {
1535 let mut registry_dirty = false;
1536 for cross_ref in entity.cross_refs() {
1537 if cross_ref.target_collection.is_empty() {
1538 continue;
1539 }
1540 {
1541 let mut forward = self.cross_refs.write();
1542 let refs = forward.entry(cross_ref.source).or_default();
1543 let inserted = !refs.iter().any(|(id, kind, coll)| {
1544 *id == cross_ref.target
1545 && *kind == cross_ref.ref_type
1546 && coll == &cross_ref.target_collection
1547 });
1548 if inserted {
1549 refs.push((
1550 cross_ref.target,
1551 cross_ref.ref_type,
1552 cross_ref.target_collection.clone(),
1553 ));
1554 registry_dirty = true;
1555 }
1556 }
1557
1558 {
1559 let mut reverse = self.reverse_refs.write();
1560 let refs = reverse.entry(cross_ref.target).or_default();
1561 let inserted = !refs.iter().any(|(id, kind, coll)| {
1562 *id == cross_ref.source && *kind == cross_ref.ref_type && coll == collection
1563 });
1564 if inserted {
1565 refs.push((cross_ref.source, cross_ref.ref_type, collection.to_string()));
1566 registry_dirty = true;
1567 }
1568 }
1569 }
1570
1571 if registry_dirty {
1572 self.mark_paged_registry_dirty();
1573 }
1574
1575 Ok(())
1576 }
1577
1578 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1580 self.cross_refs.write().remove(&id);
1582
1583 let mut reverse = self.reverse_refs.write();
1585 for refs in reverse.values_mut() {
1586 refs.retain(|(source, _, _)| *source != id);
1587 }
1588 reverse.remove(&id);
1589 self.mark_paged_registry_dirty();
1590
1591 Ok(())
1592 }
1593
1594 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1595 if ids.is_empty() {
1596 return Ok(());
1597 }
1598
1599 let id_set: std::collections::HashSet<EntityId> = ids.iter().copied().collect();
1600
1601 let needs_forward_cleanup = {
1615 let forward = self.cross_refs.read();
1616 id_set.iter().any(|id| forward.contains_key(id))
1617 };
1618 let needs_reverse_cleanup = {
1619 let reverse = self.reverse_refs.read();
1620 id_set.iter().any(|id| reverse.contains_key(id))
1621 };
1622
1623 if !needs_forward_cleanup && !needs_reverse_cleanup {
1624 self.unindex_cross_refs_fast_path
1625 .fetch_add(1, Ordering::Relaxed);
1626 return Ok(());
1627 }
1628
1629 if needs_forward_cleanup {
1630 let mut forward = self.cross_refs.write();
1631 for id in &id_set {
1632 forward.remove(id);
1633 }
1634 }
1635
1636 if needs_reverse_cleanup || needs_forward_cleanup {
1637 let mut reverse = self.reverse_refs.write();
1641 if needs_forward_cleanup {
1642 for refs in reverse.values_mut() {
1643 refs.retain(|(source, _, _)| !id_set.contains(source));
1644 }
1645 }
1646 reverse.retain(|target, refs| !id_set.contains(target) && !refs.is_empty());
1647 }
1648 self.mark_paged_registry_dirty();
1649
1650 Ok(())
1651 }
1652
1653 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1657 self.unindex_cross_refs_fast_path.load(Ordering::Relaxed)
1658 }
1659
1660 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1662 where
1663 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1664 {
1665 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1676 let collections = self.collections.read();
1677 collections
1678 .iter()
1679 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1680 .collect()
1681 };
1682
1683 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1684 if !use_parallel {
1685 return pairs
1687 .into_iter()
1688 .flat_map(|(name, mgr)| {
1689 mgr.query_all(filter.clone())
1690 .into_iter()
1691 .map(move |e| (name.clone(), e))
1692 })
1693 .collect();
1694 }
1695
1696 let filter_ref = &filter;
1698 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|s| {
1699 pairs
1700 .iter()
1701 .map(|(name, manager)| {
1702 let name = (*name).clone();
1703 s.spawn(move || {
1704 manager
1705 .query_all(|e| filter_ref(e))
1706 .into_iter()
1707 .map(|e| (name.clone(), e))
1708 .collect::<Vec<_>>()
1709 })
1710 })
1711 .collect::<Vec<_>>()
1712 .into_iter()
1713 .map(|h| h.join().unwrap_or_default())
1714 .collect()
1715 });
1716
1717 collection_results.into_iter().flatten().collect()
1718 }
1719
1720 pub fn scan<F>(
1722 &self,
1723 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1724 filter: F,
1725 ) -> Vec<(String, UnifiedEntity)>
1726 where
1727 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1728 {
1729 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1730 let collections = self.collections.read();
1731 collections
1732 .iter()
1733 .map(|(name, manager)| (name.clone(), Arc::clone(manager)))
1734 .collect()
1735 };
1736
1737 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1738 if !use_parallel {
1739 return pairs
1740 .into_iter()
1741 .flat_map(|(name, manager)| {
1742 manager
1743 .scan(snapshot, filter.clone())
1744 .into_iter()
1745 .map(move |entity| (name.clone(), entity))
1746 })
1747 .collect();
1748 }
1749
1750 let filter_ref = &filter;
1751 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|scope| {
1752 pairs
1753 .iter()
1754 .map(|(name, manager)| {
1755 let name = name.clone();
1756 scope.spawn(move || {
1757 manager
1758 .scan(snapshot, |entity| filter_ref(entity))
1759 .into_iter()
1760 .map(|entity| (name.clone(), entity))
1761 .collect::<Vec<_>>()
1762 })
1763 })
1764 .collect::<Vec<_>>()
1765 .into_iter()
1766 .map(|handle| handle.join().unwrap_or_default())
1767 .collect()
1768 });
1769
1770 collection_results.into_iter().flatten().collect()
1771 }
1772
1773 pub fn filter_metadata_all(
1775 &self,
1776 filters: &[(String, MetadataFilter)],
1777 ) -> Vec<(String, EntityId)> {
1778 let mut results = Vec::new();
1779 let collections = self.collections.read();
1780
1781 for (name, manager) in collections.iter() {
1782 for id in manager.filter_metadata(filters) {
1783 results.push((name.clone(), id));
1784 }
1785 }
1786
1787 results
1788 }
1789
1790 pub fn stats(&self) -> StoreStats {
1792 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1798 let collections = self.collections.read();
1799 collections
1800 .iter()
1801 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1802 .collect()
1803 };
1804
1805 let mut stats = StoreStats {
1806 collection_count: pairs.len(),
1807 ..Default::default()
1808 };
1809
1810 for (name, manager) in &pairs {
1811 let manager_stats = manager.stats();
1812 stats.total_entities += manager_stats.total_entities;
1813 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1814 stats.collections.insert(name.clone(), manager_stats);
1815 }
1816
1817 stats
1818 }
1819
1820 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1822 let collections = self.collections.read();
1823 for manager in collections.values() {
1824 manager.run_maintenance()?;
1825 }
1826 Ok(())
1827 }
1828
1829 pub fn reclaimable_segment_bytes(&self) -> u64 {
1831 let collections = self.collections.read();
1832 collections
1833 .values()
1834 .map(|manager| manager.reclaimable_bytes())
1835 .fold(0, u64::saturating_add)
1836 }
1837
1838 pub fn pressure_consolidation_tick(&self) -> bool {
1841 let managers: Vec<_> = self.collections.read().values().cloned().collect();
1842 let mut advanced = false;
1845 for manager in &managers {
1846 advanced |= manager.pressure_consolidation_tick();
1847 }
1848 advanced
1849 }
1850}
1851
1852fn flatten_config_json(
1854 prefix: &str,
1855 value: &crate::serde_json::Value,
1856 out: &mut Vec<(String, reddb_types::Value)>,
1857) {
1858 use reddb_types::Value;
1859 match value {
1860 crate::serde_json::Value::Object(map) => {
1861 for (k, v) in map {
1862 let key = if prefix.is_empty() {
1863 k.clone()
1864 } else {
1865 format!("{prefix}.{k}")
1866 };
1867 flatten_config_json(&key, v, out);
1868 }
1869 }
1870 crate::serde_json::Value::String(s) => {
1871 out.push((prefix.to_string(), Value::text(s.clone())));
1872 }
1873 crate::serde_json::Value::Integer(n) => {
1874 out.push((prefix.to_string(), Value::Integer(*n)));
1875 }
1876 crate::serde_json::Value::Number(n) => {
1877 if n.fract().abs() < f64::EPSILON {
1878 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1879 } else {
1880 out.push((prefix.to_string(), Value::Float(*n)));
1881 }
1882 }
1883 crate::serde_json::Value::Decimal(n) => {
1884 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1885 }
1886 crate::serde_json::Value::Bool(b) => {
1887 out.push((prefix.to_string(), Value::Boolean(*b)));
1888 }
1889 crate::serde_json::Value::Null => {
1890 out.push((prefix.to_string(), Value::Null));
1891 }
1892 crate::serde_json::Value::Array(arr) => {
1893 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1894 out.push((prefix.to_string(), Value::text(json_str)));
1895 }
1896 }
1897}