1use super::*;
2
3impl UnifiedStore {
4 pub(crate) fn canonical_collection(name: &str) -> &str {
5 reddb_engine::storage::system_collection_names::canonical_or_self(name)
6 }
7
8 pub(crate) fn insert_vault_version(
12 &self,
13 collection: &str,
14 entity: UnifiedEntity,
15 metadata: Metadata,
16 ) -> Result<(), StoreError> {
17 assert!(matches!(entity.data, EntityData::Row(_)));
18 let manager = self
19 .get_collection(collection)
20 .ok_or_else(|| StoreError::CollectionNotFound(collection.into()))?;
21 self.pending_vault_versions.write().insert(entity.id);
22 let id = manager.insert(entity)?;
23 self.register_entity_id(id);
24 if !metadata.fields.is_empty() {
25 manager.set_metadata(id, metadata)?;
26 }
27 let entity = manager
28 .get(id)
29 .expect("inserted vault version remains in memory");
30 self.persist_entities_to_pager_wal_only(collection, &[entity])
31 }
32
33 pub(crate) fn persist_entities_to_pager(
34 &self,
35 collection: &str,
36 entities: &[UnifiedEntity],
37 ) -> Result<(), StoreError> {
38 self.persist_entities_impl(collection, entities, false)
39 }
40
41 pub(crate) fn persist_entity_refs_to_pager(
45 &self,
46 collection: &str,
47 entities: &[&UnifiedEntity],
48 ) -> Result<(), StoreError> {
49 self.persist_entity_refs_impl(collection, entities, false)
50 }
51
52 pub(crate) fn persist_entity_refs_to_pager_wal_only(
55 &self,
56 collection: &str,
57 entities: &[&UnifiedEntity],
58 ) -> Result<(), StoreError> {
59 self.persist_entity_refs_impl(collection, entities, true)
60 }
61
62 fn persist_entity_refs_impl(
63 &self,
64 collection: &str,
65 entities: &[&UnifiedEntity],
66 skip_btree: bool,
67 ) -> Result<(), StoreError> {
68 let collection = Self::canonical_collection(collection);
69 let skip_btree = skip_btree || self.config.resident_memory_bytes_max.is_some();
70 if entities.is_empty() {
71 return Ok(());
72 }
73 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
74 return Ok(());
75 }
76 let fv = self.format_version();
77 let manager = self
78 .get_collection(collection)
79 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
80 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
81 .iter()
82 .map(|entity| {
83 let metadata = manager.get_metadata(entity.id);
84 (
85 entity.id.raw().to_be_bytes().to_vec(),
86 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
87 )
88 })
89 .collect();
90 serialized.sort_by(|a, b| a.0.cmp(&b.0));
91
92 if !skip_btree
93 && !Self::deferred_store_wal_capture_active()
94 && self.config.resident_memory_bytes_max.is_none()
95 {
96 let Some(pager) = &self.pager else {
97 let records: Vec<Vec<u8>> =
98 serialized.into_iter().map(|(_id, record)| record).collect();
99 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
100 collection: collection.to_string(),
101 records,
102 }])?;
103 return Ok(());
104 };
105 let mut btree_indices = self.btree_indices.write();
106 let btree = btree_indices
107 .entry(collection.to_string())
108 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
109 let root_before = btree.root_page_id();
110 btree.upsert_batch_sorted(&serialized).map_err(|e| {
111 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
112 })?;
113 let root_after = btree.root_page_id();
114 drop(btree_indices);
115 if root_before != root_after {
116 self.mark_paged_registry_dirty();
117 }
118 }
119 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
120 self.finish_paged_write_for_projection(
121 [StoreWalAction::BulkUpsertEntityRecords {
122 collection: collection.to_string(),
123 records,
124 }],
125 skip_btree,
126 )?;
127 Ok(())
128 }
129
130 pub(crate) fn persist_entities_to_pager_wal_only(
142 &self,
143 collection: &str,
144 entities: &[UnifiedEntity],
145 ) -> Result<(), StoreError> {
146 self.persist_entities_impl(collection, entities, true)
147 }
148
149 fn persist_entities_impl(
150 &self,
151 collection: &str,
152 entities: &[UnifiedEntity],
153 skip_btree: bool,
154 ) -> Result<(), StoreError> {
155 let collection = Self::canonical_collection(collection);
156 let skip_btree = skip_btree || self.config.resident_memory_bytes_max.is_some();
157 if entities.is_empty() {
158 return Ok(());
159 }
160
161 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
162 return Ok(());
163 }
164
165 let fv = self.format_version();
166 let manager = self
167 .get_collection(collection)
168 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
169 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
170 .iter()
171 .map(|entity| {
172 let metadata = manager.get_metadata(entity.id);
173 (
174 entity.id.raw().to_be_bytes().to_vec(),
175 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
176 )
177 })
178 .collect();
179 serialized.sort_by(|a, b| a.0.cmp(&b.0));
181
182 if !skip_btree
183 && !Self::deferred_store_wal_capture_active()
184 && self.config.resident_memory_bytes_max.is_none()
185 {
186 let Some(pager) = &self.pager else {
187 let records: Vec<Vec<u8>> =
188 serialized.into_iter().map(|(_id, record)| record).collect();
189 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
190 collection: collection.to_string(),
191 records,
192 }])?;
193 return Ok(());
194 };
195 let mut btree_indices = self.btree_indices.write();
196 let btree = btree_indices
197 .entry(collection.to_string())
198 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
199 let root_before = btree.root_page_id();
200
201 btree.upsert_batch_sorted(&serialized).map_err(|e| {
205 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
206 })?;
207 let root_after = btree.root_page_id();
208 drop(btree_indices);
209 if root_before != root_after {
210 self.mark_paged_registry_dirty();
211 }
212 }
213 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
223 self.finish_paged_write_for_projection(
224 [StoreWalAction::BulkUpsertEntityRecords {
225 collection: collection.to_string(),
226 records,
227 }],
228 skip_btree,
229 )?;
230
231 Ok(())
232 }
233
234 pub(crate) fn update_graph_label_index(
236 &self,
237 collection: &str,
238 label: &str,
239 entity_id: EntityId,
240 ) {
241 self.resident
242 .update_graph_label_index(collection, label, entity_id)
243 }
244
245 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
247 self.resident
248 .remove_from_graph_label_index(collection, entity_id)
249 }
250
251 pub(crate) fn remove_from_graph_label_index_batch(
252 &self,
253 collection: &str,
254 entity_ids: &[EntityId],
255 ) {
256 self.resident
257 .remove_from_graph_label_index_batch(collection, entity_ids)
258 }
259
260 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
262 self.resident.lookup_graph_nodes_by_label(label)
263 }
264
265 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
267 self.resident
268 .lookup_graph_nodes_by_label_in(collection, label)
269 }
270
271 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
272 let name = reddb_engine::storage::system_collection_names::canonical_owned(name.into());
273 let mut collections = self.resident.collections.write();
274
275 if collections.contains_key(&name) {
276 return Err(StoreError::CollectionExists(name));
277 }
278
279 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
280 collections.insert(name.clone(), Arc::new(manager));
281 drop(collections);
282
283 let create = match self.prepare_operational_collection_create(&name) {
284 Ok(create) => create,
285 Err(err) => {
286 let mut collections = self.resident.collections.write();
287 collections.remove(&name);
288 return Err(err);
289 }
290 };
291 if let Some(create) = create {
292 if let Err(err) = create.publish().map_err(StoreError::Io) {
293 let mut collections = self.resident.collections.write();
294 collections.remove(&name);
295 return Err(err);
296 }
297 }
298
299 self.mark_paged_registry_dirty();
300 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
301
302 Ok(())
303 }
304
305 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
307 self.resident.get_or_create_collection(
308 reddb_engine::storage::system_collection_names::canonical_owned(name.into()),
309 &self.config.manager_config,
310 )
311 }
312
313 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
315 self.resident
316 .get_collection(Self::canonical_collection(name))
317 }
318
319 pub fn context_index(&self) -> &ContextIndex {
321 self.resident.context_index()
322 }
323
324 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
330 let mut map = self.replayed_turbo_inserts.lock();
331 map.remove(Self::canonical_collection(collection))
332 }
333
334 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
335 let mut deltas = self.replayed_probabilistic_deltas.lock();
336 std::mem::take(&mut *deltas)
337 }
338
339 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
346 self.try_set_config_tree(prefix, json).unwrap_or_default()
349 }
350
351 pub fn try_set_config_tree(
352 &self,
353 prefix: &str,
354 json: &crate::serde_json::Value,
355 ) -> Result<usize, StoreError> {
356 let _publication = self.vault_publication_lock.lock();
357 let manager = self.get_or_create_collection("red_config");
358 let mut pairs = Vec::new();
359 flatten_config_json(prefix, json, &mut pairs);
360 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
362 .iter()
363 .map(|(key, _)| (key.clone(), Vec::new()))
364 .collect();
365 let mut capture = |entity: &UnifiedEntity| {
366 if let EntityData::Row(row) = &entity.data {
367 if let Some(reddb_types::Value::Text(key)) = row.get_field("key") {
368 if let Some(rows) = previous.get_mut(key.as_ref()) {
369 rows.push((entity.id, row.get_field("value").cloned()));
370 }
371 }
372 }
373 true
374 };
375 if manager.has_backing() {
376 for (key, _) in &pairs {
377 manager.try_for_each_key_entity(key, &mut capture)?;
378 }
379 } else {
380 manager.try_for_each_entity(usize::MAX, &mut capture)?;
381 }
382 let mut saved = 0;
383 for (key, value) in pairs {
384 let stale = previous.remove(&key).unwrap_or_default();
385 if let [(_, Some(current))] = stale.as_slice() {
389 if *current == value {
390 saved += 1;
391 continue;
392 }
393 }
394 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
395 let entity = UnifiedEntity::new(
399 EntityId::new(0),
400 EntityKind::TableRow {
401 table: Arc::from("red_config"),
402 row_id: 0,
403 },
404 EntityData::Row(RowData {
405 columns: Vec::new(),
406 named: Some(
407 [
408 ("key".to_string(), reddb_types::Value::text(key)),
409 ("value".to_string(), value),
410 ]
411 .into_iter()
412 .collect(),
413 ),
414 schema: None,
415 }),
416 );
417 self.insert_auto("red_config", entity)?;
418 saved += 1;
419 if !stale.is_empty() {
420 self.delete_batch("red_config", &stale)?;
421 }
422 }
423 Ok(saved)
424 }
425
426 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
430 self.try_get_config(key).ok().flatten()
433 }
434
435 pub fn try_get_config(&self, key: &str) -> Result<Option<reddb_types::Value>, StoreError> {
436 let Some(manager) = self.get_collection("red_config") else {
437 return Ok(None);
438 };
439 let _publication = manager
440 .has_backing()
441 .then(|| self.vault_publication_lock.lock());
442 let mut latest: Option<(u64, reddb_types::Value)> = None;
443 manager.try_for_each_key_entity(key, |entity| {
444 if let EntityData::Row(row) = &entity.data {
445 let key_matches = matches!(row.get_field("key"), Some(reddb_types::Value::Text(stored)) if stored.as_ref() == key);
446 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
447 if let Some(value) = row.get_field("value") {
448 latest = Some((entity.id.raw(), value.clone()));
449 }
450 }
451 }
452 true
453 })?;
454 Ok(latest.map(|(_, value)| value))
455 }
456
457 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
462 self.resident.set_aux_metadata(bytes)
463 }
464
465 pub(crate) fn persist_aux_metadata(&self, bytes: Vec<u8>) -> Result<(), StoreError> {
468 let bytes = reddb_engine::catalog::codec::canonicalize_collection_contract_blob(&bytes)
469 .map_err(|err| StoreError::Serialization(err.to_string()))?;
470 let _publication = self.vault_publication_lock.lock();
471 self.finish_paged_write([StoreWalAction::SetAuxMetadata {
472 bytes: bytes.clone(),
473 }])?;
474 self.resident.set_aux_metadata(bytes);
475 Ok(())
476 }
477
478 pub fn aux_metadata(&self) -> Vec<u8> {
480 self.resident.aux_metadata()
481 }
482
483 pub fn list_collections(&self) -> Vec<String> {
485 self.resident.list_collections()
486 }
487
488 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
490 let name = Self::canonical_collection(name);
491 let _vault_guard = self.vault_publication_lock.lock();
492 let drop = self.prepare_operational_collection_drop(name)?;
493 let manager = {
494 let mut collections = self.resident.collections.write();
495
496 collections
497 .remove(name)
498 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
499 };
500
501 let entities = manager.query_all(|_| true);
502 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
503
504 for entity_id in &entity_ids {
505 self.resident.context_index.remove_entity(*entity_id);
506 let _ = self.unindex_cross_refs(*entity_id);
507 }
508
509 if let Some(tree) = self.btree_indices.write().remove(name) {
510 self.retire_btree(tree);
511 }
512 if let Some(index) = self.key_indices.write().remove(name) {
513 for tree in index.trees() {
514 self.retire_btree(Arc::clone(tree));
515 }
516 }
517 self.secondary_index_specs.write().remove(name);
518
519 self.resident
520 .entity_cache
521 .retain(|entity_id, (collection, _)| {
522 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
523 });
524
525 self.resident.cross_refs.write().retain(|source_id, refs| {
526 refs.retain(|(target_id, _, target_collection)| {
527 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
528 });
529 !entity_ids.iter().any(|id| id == source_id)
530 });
531
532 self.resident
533 .reverse_refs
534 .write()
535 .retain(|target_id, refs| {
536 refs.retain(|(source_id, _, source_collection)| {
537 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
538 });
539 !entity_ids.iter().any(|id| id == target_id)
540 });
541
542 self.mark_paged_registry_dirty();
543 self.finish_paged_write([StoreWalAction::DropCollection {
544 name: name.to_string(),
545 }])?;
546 if let Some(drop) = drop {
547 drop.finish().map_err(StoreError::Io)?;
548 }
549
550 Ok(())
551 }
552
553 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
555 let collection = Self::canonical_collection(collection);
556 let manager = self
557 .get_collection(collection)
558 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
559
560 let mut entity = entity;
561 entity.canonicalize_system_collection_names();
562 if entity.id.raw() == 0 {
563 entity.id = self.next_entity_id();
564 } else {
565 self.register_entity_id(entity.id);
566 }
567 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
569 if *row_id == 0 {
570 *row_id = manager.next_row_id();
571 } else {
572 manager.register_row_id(*row_id);
573 }
574 }
575 entity.ensure_table_logical_id();
576 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
578 {
579 Some(node.label.clone())
580 } else {
581 None
582 };
583
584 let id = manager.insert(entity)?;
585 self.register_entity_id(id);
586
587 if let Some(ref label) = graph_node_label {
589 self.update_graph_label_index(collection, label, id);
590 }
591
592 let mut registry_dirty = false;
595 if let Some(pager) = self.pager.as_ref().filter(|_| {
596 !Self::deferred_store_wal_capture_active()
597 && self.config.resident_memory_bytes_max.is_none()
598 }) {
599 if let Some(entity) = manager.get(id) {
600 let mut btree_indices = self.btree_indices.write();
601 let btree = btree_indices
602 .entry(collection.to_string())
603 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
604 let root_before = btree.root_page_id();
605
606 let key = id.raw().to_be_bytes();
607 let metadata = manager.get_metadata(id);
608 let value = Self::serialize_entity_record(
609 &entity,
610 metadata.as_ref(),
611 self.format_version(),
612 );
613 let _ = btree.insert(&key, &value);
615 registry_dirty = root_before != btree.root_page_id();
616 }
617 }
618
619 if self.config.auto_index_refs {
621 if let Some(entity) = manager.get(id) {
622 self.index_cross_refs(&entity, collection)?;
623 }
624 }
625
626 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
630 let actions = manager
631 .get(id)
632 .map(|entity| {
633 let metadata = manager.get_metadata(id);
634 vec![StoreWalAction::upsert_entity(
635 collection,
636 &entity,
637 metadata.as_ref(),
638 self.format_version(),
639 )]
640 })
641 .unwrap_or_default();
642 if registry_dirty {
643 self.mark_paged_registry_dirty();
644 }
645 self.finish_paged_write(actions)?;
646 }
647
648 Ok(id)
649 }
650
651 pub fn bulk_insert(
656 &self,
657 collection: &str,
658 mut entities: Vec<UnifiedEntity>,
659 ) -> Result<Vec<EntityId>, StoreError> {
660 let collection = Self::canonical_collection(collection);
661 for entity in &mut entities {
662 entity.canonicalize_system_collection_names();
663 }
664 let _publication = self.vault_publication_lock.lock();
667 let trace = matches!(
671 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
672 Some("1") | Some("true") | Some("on")
673 );
674 let t_start = std::time::Instant::now();
675 let n = entities.len();
676 let manager = self.get_or_create_collection(collection);
677 let t_get_coll = t_start.elapsed();
678
679 let t0 = std::time::Instant::now();
688 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
689 let n_missing_row_ids = entities
690 .iter()
691 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
692 .count() as u64;
693 let mut entity_id_range = if n_missing_entity_ids > 0 {
694 self.reserve_entity_ids(n_missing_entity_ids)
695 } else {
696 0..0
697 };
698 let mut row_id_range = if n_missing_row_ids > 0 {
699 manager.reserve_row_ids(n_missing_row_ids)
700 } else {
701 0..0
702 };
703 for entity in &mut entities {
704 if entity.id.raw() == 0 {
705 let next = entity_id_range
706 .next()
707 .expect("reserved entity-id range exhausted");
708 entity.id = EntityId::new(next);
709 } else {
710 self.register_entity_id(entity.id);
711 }
712 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
713 if *row_id == 0 {
714 *row_id = row_id_range
715 .next()
716 .expect("reserved row-id range exhausted");
717 } else {
718 manager.register_row_id(*row_id);
719 }
720 }
721 entity.ensure_table_logical_id();
722 }
723 let t_assign_ids = t0.elapsed();
724
725 let graph_labels: Vec<Option<(String, EntityId)>> = entities
727 .iter()
728 .map(|e| {
729 if let EntityKind::GraphNode(ref node) = e.kind {
730 Some((node.label.clone(), e.id))
731 } else {
732 None
733 }
734 })
735 .collect();
736
737 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 let t0 = std::time::Instant::now();
753 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
754 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
755 let fv = self.format_version();
756 let serial_map = |id: &EntityId| {
757 manager
758 .get_with(*id, |entity| {
759 (
760 id.raw().to_be_bytes().to_vec(),
761 Self::serialize_entity_record(entity, None, fv),
762 )
763 })
764 .expect("bulk-inserted entity must remain in the fenced segment")
765 };
766 if ids.len() >= 512 {
772 use rayon::prelude::*;
773 Some(ids.par_iter().map(serial_map).collect())
774 } else {
775 Some(ids.iter().map(serial_map).collect())
776 }
777 } else {
778 None
779 };
780 let t_serialize = t0.elapsed();
781
782 for (label, entity_id) in graph_labels.iter().flatten() {
784 self.update_graph_label_index(collection, label, *entity_id);
785 }
786
787 let skip_btree_requested = matches!(
797 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
798 .ok()
799 .as_deref(),
800 Some("1") | Some("true") | Some("on")
801 );
802 let skip_btree = skip_btree_requested && self.pager.is_none();
805 if skip_btree_requested && !skip_btree {
806 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
808 IGNORED.get_or_init(|| {
809 tracing::warn!(
810 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
811 active — flag ignored; bulk inserts will be persisted normally"
812 );
813 });
814 } else if skip_btree {
815 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
817 WARNED.get_or_init(|| {
818 tracing::warn!(
819 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
820 bulk inserts NOT durable; data will be lost on restart"
821 );
822 });
823 }
824
825 let mut t_btree_lock = std::time::Duration::ZERO;
829 let mut t_btree_insert = std::time::Duration::ZERO;
830 let mut t_flush = std::time::Duration::ZERO;
831 if !skip_btree
832 && !Self::deferred_store_wal_capture_active()
833 && self.config.resident_memory_bytes_max.is_none()
834 {
835 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
836 let t0 = std::time::Instant::now();
837 let mut btree_indices = self.btree_indices.write();
838 let btree = btree_indices
839 .entry(collection.to_string())
840 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
841 let root_before = btree.root_page_id();
842 t_btree_lock = t0.elapsed();
843
844 let t0 = std::time::Instant::now();
845 let _ = btree.bulk_insert_sorted(batch);
846 t_btree_insert = t0.elapsed();
847 let registry_dirty = root_before != btree.root_page_id();
848
849 let t0 = std::time::Instant::now();
850 if registry_dirty {
851 self.mark_paged_registry_dirty();
852 }
853 t_flush = t0.elapsed();
854 }
855 }
856
857 let actions = serialized
863 .map(|batch| {
864 let records: Vec<Vec<u8>> =
865 batch.into_iter().map(|(_key, record)| record).collect();
866 vec![StoreWalAction::BulkUpsertEntityRecords {
867 collection: collection.to_string(),
868 records,
869 }]
870 })
871 .unwrap_or_default();
872 self.finish_paged_write(actions)?;
873
874 if trace {
875 tracing::debug!(
876 n,
877 total = ?t_start.elapsed(),
878 get_coll = ?t_get_coll,
879 assign = ?t_assign_ids,
880 serialize = ?t_serialize,
881 manager = ?t_manager,
882 btree_lock = ?t_btree_lock,
883 btree = ?t_btree_insert,
884 flush = ?t_flush,
885 "bulk_insert timing"
886 );
887 }
888
889 Ok(ids)
890 }
891
892 pub fn insert_auto(
894 &self,
895 collection: &str,
896 entity: UnifiedEntity,
897 ) -> Result<EntityId, StoreError> {
898 let collection = Self::canonical_collection(collection);
899 let _publication = self.vault_publication_lock.lock();
901 let manager = self.get_or_create_collection(collection);
902 let mut entity = entity;
903 entity.canonicalize_system_collection_names();
904 if entity.id.raw() == 0 {
905 entity.id = self.next_entity_id();
906 } else {
907 self.register_entity_id(entity.id);
908 }
909 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
911 if *row_id == 0 {
912 *row_id = manager.next_row_id();
913 } else {
914 manager.register_row_id(*row_id);
915 }
916 }
917 entity.ensure_table_logical_id();
918
919 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
921 {
922 Some(node.label.clone())
923 } else {
924 None
925 };
926 self.resident
928 .context_index
929 .index_entity(collection, &entity);
930
931 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
934 if self.config.auto_index_refs {
935 self.index_cross_refs(&entity, collection)?;
936 }
937
938 let paged_btree = if persist_record
945 && !Self::deferred_store_wal_capture_active()
946 && self.config.resident_memory_bytes_max.is_none()
947 {
948 self.get_or_create_btree(collection)
949 } else {
950 None
951 };
952 let persist_fence = paged_btree
953 .as_ref()
954 .map(|_| self.btree_indices.read_recursive());
955 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
956 let unversioned = entity.xmin == 0 && entity.xmax == 0;
957
958 let id = manager.insert(entity)?;
959 let serialized_record = if persist_record {
960 Some(
961 manager
962 .get_with(id, |entity| {
963 Self::serialize_entity_record(entity, None, self.format_version())
964 })
965 .ok_or(StoreError::EntityNotFound(id))?,
966 )
967 } else {
968 None
969 };
970 if let Some(ref label) = graph_node_label {
978 self.update_graph_label_index(collection, label, id);
979 }
980
981 let mut registry_dirty = false;
982 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
983 let root_before = btree.root_page_id();
984
985 let key = id.raw().to_be_bytes();
986 btree.insert(&key, record).map_err(|e| {
987 StoreError::Io(std::io::Error::other(format!(
988 "B-tree insert error while inserting '{collection}'/{id}: {e}"
989 )))
990 })?;
991 registry_dirty = root_before != btree.root_page_id();
992 }
993 drop(persist_fence);
994
995 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
999 let actions = serialized_record
1000 .map(|record| {
1001 vec![StoreWalAction::UpsertEntityRecord {
1002 collection: collection.to_string(),
1003 record,
1004 }]
1005 })
1006 .unwrap_or_default();
1007 if registry_dirty {
1008 self.mark_paged_registry_dirty();
1009 }
1010 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1011 drop(_publication);
1012 self.complete_grouped_actions(&pending, &actions)?;
1013 } else {
1014 self.finish_paged_write(actions)?;
1015 }
1016 }
1017 if unversioned && !Self::deferred_store_wal_capture_active() {
1018 if let Some(before) = before {
1019 self.note_paged_insert(collection, &manager, &before, id);
1020 }
1021 }
1022 Ok(id)
1023 }
1024
1025 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
1031 self.try_get(collection, id, usize::MAX).ok().flatten()
1034 }
1035
1036 pub fn try_get(
1037 &self,
1038 collection: &str,
1039 id: EntityId,
1040 limit_bytes: usize,
1041 ) -> Result<Option<UnifiedEntity>, StoreError> {
1042 let Some(manager) = self.get_collection(collection) else {
1043 return Ok(None);
1044 };
1045 manager.try_get(id, limit_bytes)
1046 }
1047
1048 pub fn get_table_row_by_logical_id(
1054 &self,
1055 collection: &str,
1056 logical_id: EntityId,
1057 ) -> Option<UnifiedEntity> {
1058 if let Some(entity) = self.get(collection, logical_id) {
1059 if matches!(entity.kind, EntityKind::TableRow { .. })
1060 && entity.logical_id() == logical_id
1061 && entity.xmax == 0
1062 {
1063 return Some(entity);
1064 }
1065 }
1066
1067 let manager = self.get_collection(collection)?;
1068 let mut matches = manager.query_all(|entity| {
1069 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1070 });
1071 matches
1072 .iter()
1073 .find(|entity| entity.xmax == 0)
1074 .cloned()
1075 .or_else(|| matches.pop())
1076 }
1077
1078 pub fn table_row_versions_by_logical_id(
1079 &self,
1080 collection: &str,
1081 logical_id: EntityId,
1082 ) -> Vec<UnifiedEntity> {
1083 self.resident
1084 .table_row_versions_by_logical_id(collection, logical_id)
1085 }
1086
1087 pub(crate) fn try_for_each_table_row_version(
1088 &self,
1089 collection: &str,
1090 logical_id: EntityId,
1091 mut visit: impl FnMut(&UnifiedEntity) -> bool,
1092 ) -> Result<(), StoreError> {
1093 let Some(manager) = self.get_collection(collection) else {
1094 return Ok(());
1095 };
1096 manager.try_for_each_entity(usize::MAX, |entity| {
1097 !matches!(entity.kind, EntityKind::TableRow { .. })
1098 || entity.logical_id() != logical_id
1099 || visit(entity)
1100 })
1101 }
1102
1103 pub(crate) fn try_get_table_row_by_logical_id(
1104 &self,
1105 collection: &str,
1106 logical_id: EntityId,
1107 ) -> Result<Option<UnifiedEntity>, StoreError> {
1108 let Some(manager) = self.get_collection(collection) else {
1109 return Ok(None);
1110 };
1111 if let Some(entity) = manager.try_get(logical_id, usize::MAX)? {
1112 if matches!(entity.kind, EntityKind::TableRow { .. })
1113 && entity.logical_id() == logical_id
1114 && entity.xmax == 0
1115 {
1116 return Ok(Some(entity));
1117 }
1118 }
1119 let mut selected = None;
1122 self.try_for_each_table_row_version(collection, logical_id, |entity| {
1123 selected = Some(entity.clone());
1124 entity.xmax != 0
1125 })?;
1126 manager.restore_owned_row_schemas(selected.iter_mut());
1127 Ok(selected)
1128 }
1129
1130 pub fn vacuum_mvcc_history(
1131 &self,
1132 collection: &str,
1133 cutoff_xid: u64,
1134 ) -> Result<MvccVacuumStats, StoreError> {
1135 let manager = self
1136 .get_collection(collection)
1137 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1138 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1139 manager.for_each_entity(|entity| {
1140 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1141 return true;
1142 }
1143 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1144 if entity.xmax == 0 {
1145 entry.0 = true;
1146 }
1147 entry.1 = entry.1.max(entity.xmin);
1148 true
1149 });
1150
1151 let mut stats = MvccVacuumStats::default();
1152 let mut reclaim_ids = Vec::new();
1153 manager.for_each_entity(|entity| {
1154 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1155 return true;
1156 }
1157 stats.scanned_versions += 1;
1158 let (has_live_version, max_xmin) = logical
1159 .get(&entity.logical_id())
1160 .copied()
1161 .unwrap_or((false, entity.xmin));
1162 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1163 if entity.xmax < cutoff_xid {
1164 stats.reclaimed_versions += 1;
1165 if is_delete_tombstone {
1166 stats.reclaimed_tombstones += 1;
1167 } else {
1168 stats.reclaimed_history_versions += 1;
1169 }
1170 reclaim_ids.push(entity.id);
1171 } else {
1172 stats.retained_versions += 1;
1173 if is_delete_tombstone {
1174 stats.retained_tombstones += 1;
1175 } else {
1176 stats.retained_history_versions += 1;
1177 }
1178 }
1179 true
1180 });
1181
1182 if !reclaim_ids.is_empty() {
1183 self.delete_batch(collection, &reclaim_ids)?;
1184 }
1185 Ok(stats)
1186 }
1187
1188 pub(crate) fn install_versioned_table_row_update(
1189 &self,
1190 collection: &str,
1191 old_version: UnifiedEntity,
1192 mut new_version: UnifiedEntity,
1193 metadata: Option<&Metadata>,
1194 ) -> Result<(), StoreError> {
1195 let manager = self
1196 .get_collection(collection)
1197 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1198
1199 let old_id = old_version.id;
1200 let new_id = new_version.id;
1201 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1202
1203 self.resident.entity_cache.remove(old_id.raw());
1204 self.resident.entity_cache.remove(new_id.raw());
1205 manager.update(old_version.clone())?;
1206 self.resident.context_index.remove_entity(old_id);
1207
1208 self.register_entity_id(new_version.id);
1209 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1210 if *row_id == 0 {
1211 *row_id = manager.next_row_id();
1212 } else {
1213 manager.register_row_id(*row_id);
1214 }
1215 }
1216 new_version.ensure_table_logical_id();
1217 manager.insert(new_version.clone())?;
1218 if let Some(metadata) = inherited_metadata {
1219 manager.set_metadata(new_id, metadata)?;
1220 }
1221 self.resident
1222 .context_index
1223 .index_entity(collection, &new_version);
1224 if self.config.auto_index_refs {
1225 self.index_cross_refs(&new_version, collection)?;
1226 }
1227
1228 let old_metadata = manager.get_metadata(old_id);
1229 let new_metadata = manager.get_metadata(new_id);
1230 let fv = self.format_version();
1231 let records = vec![
1234 manager
1235 .get_with(old_id, |entity| {
1236 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1237 })
1238 .ok_or(StoreError::EntityNotFound(old_id))?,
1239 manager
1240 .get_with(new_id, |entity| {
1241 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1242 })
1243 .ok_or(StoreError::EntityNotFound(new_id))?,
1244 ];
1245 self.finish_paged_write_for_projection(
1246 [StoreWalAction::BulkUpsertEntityRecords {
1247 collection: collection.to_string(),
1248 records,
1249 }],
1250 true,
1251 )?;
1252
1253 Ok(())
1254 }
1255
1256 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1262 match self.get_collection(collection) {
1263 Some(manager) => manager.get_many(ids),
1264 None => vec![None; ids.len()],
1265 }
1266 }
1267
1268 pub fn try_get_batch(
1270 &self,
1271 collection: &str,
1272 ids: &[EntityId],
1273 read_bytes_max: usize,
1274 ) -> Result<Vec<Option<UnifiedEntity>>, StoreError> {
1275 match self.get_collection(collection) {
1276 Some(manager) => manager.try_get_many(ids, read_bytes_max),
1277 None => Ok(vec![None; ids.len()]),
1278 }
1279 }
1280
1281 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1283 self.resident.get_any(id)
1284 }
1285
1286 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1292 self.resident.entity_cache_hit_rate()
1293 }
1294
1295 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1297 self.resident.entity_cache_stats()
1298 }
1299
1300 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1302 let collection = Self::canonical_collection(collection);
1303 let publication = self.vault_publication_lock.lock();
1304 self.resident.entity_cache.remove(id.raw());
1306 let manager = self
1307 .get_collection(collection)
1308 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1309
1310 let deleted = manager.delete(id)?;
1311 if !deleted {
1312 return Ok(false);
1313 }
1314
1315 let mut registry_dirty = false;
1317 if self.pager.is_some() && self.config.resident_memory_bytes_max.is_none() {
1318 let btree_indices = self.btree_indices.read();
1319 if let Some(btree) = btree_indices.get(collection) {
1320 let root_before = btree.root_page_id();
1321 let key = id.raw().to_be_bytes();
1322 let _ = btree.delete(&key);
1323 registry_dirty = root_before != btree.root_page_id();
1324 }
1325 }
1326
1327 self.unindex_cross_refs(id)?;
1329
1330 self.remove_from_graph_label_index(collection, id);
1332
1333 if registry_dirty {
1334 self.mark_paged_registry_dirty();
1335 }
1336 let actions = [StoreWalAction::DeleteEntityRecord {
1337 collection: collection.to_string(),
1338 entity_id: id.raw(),
1339 }];
1340 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1341 drop(publication);
1342 self.complete_grouped_actions(&pending, &actions)?;
1343 } else {
1344 self.finish_paged_write(actions)?;
1345 }
1346
1347 Ok(true)
1348 }
1349
1350 pub fn delete_batch(
1351 &self,
1352 collection: &str,
1353 ids: &[EntityId],
1354 ) -> Result<Vec<EntityId>, StoreError> {
1355 let collection = Self::canonical_collection(collection);
1356 if ids.is_empty() {
1357 return Ok(Vec::new());
1358 }
1359 let _publication = self.vault_publication_lock.lock();
1362
1363 self.resident
1368 .entity_cache
1369 .remove_many(ids.iter().map(|id| id.raw()));
1370
1371 let manager = self
1372 .get_collection(collection)
1373 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1374
1375 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1376 let deleted_ids = manager.delete_batch(ids)?;
1377 if deleted_ids.is_empty() {
1378 return Ok(deleted_ids);
1379 }
1380
1381 let mut registry_dirty = false;
1382 let mut updated_tree = false;
1383 if self.pager.is_some() && self.config.resident_memory_bytes_max.is_none() {
1384 let btree_indices = self.btree_indices.read();
1385 if let Some(btree) = btree_indices.get(collection) {
1386 updated_tree = true;
1387 let root_before = btree.root_page_id();
1388 for id in &deleted_ids {
1389 let key = id.raw().to_be_bytes();
1390 updated_tree &= btree.delete(&key).is_ok();
1393 }
1394 registry_dirty = root_before != btree.root_page_id();
1395 }
1396 }
1397
1398 self.unindex_cross_refs_batch(&deleted_ids)?;
1399 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1400 if registry_dirty {
1401 self.mark_paged_registry_dirty();
1402 }
1403 let actions = deleted_ids
1404 .iter()
1405 .map(|id| StoreWalAction::DeleteEntityRecord {
1406 collection: collection.to_string(),
1407 entity_id: id.raw(),
1408 })
1409 .collect::<Vec<_>>();
1410 self.finish_paged_write(actions)?;
1411
1412 if updated_tree {
1413 if let Some(before) = before {
1414 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1415 }
1416 self.flush_paged_registry()?;
1421 }
1422 Ok(deleted_ids)
1423 }
1424
1425 pub fn set_metadata(
1427 &self,
1428 collection: &str,
1429 id: EntityId,
1430 metadata: Metadata,
1431 ) -> Result<(), StoreError> {
1432 let manager = self
1433 .get_collection(collection)
1434 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1435
1436 manager.set_metadata(id, metadata)?;
1437 if let Some(entity) = manager.get(id) {
1438 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1439 }
1440 Ok(())
1441 }
1442
1443 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1445 self.get_collection(collection)?.get_metadata(id)
1446 }
1447
1448 pub fn try_get_metadata(
1450 &self,
1451 collection: &str,
1452 id: EntityId,
1453 limit_bytes: usize,
1454 ) -> Result<Option<Metadata>, StoreError> {
1455 let Some(manager) = self.get_collection(collection) else {
1456 return Ok(None);
1457 };
1458 manager.try_get_metadata(id, limit_bytes)
1459 }
1460
1461 pub fn add_cross_ref(
1463 &self,
1464 source_collection: &str,
1465 source_id: EntityId,
1466 target_collection: &str,
1467 target_id: EntityId,
1468 ref_type: RefType,
1469 weight: f32,
1470 ) -> Result<(), StoreError> {
1471 let source_collection = Self::canonical_collection(source_collection);
1472 let target_collection = Self::canonical_collection(target_collection);
1473 let registry_dirty = self.resident.add_cross_ref_in_memory(
1474 source_collection,
1475 source_id,
1476 target_collection,
1477 target_id,
1478 ref_type,
1479 weight,
1480 self.config.max_cross_refs,
1481 |entity| {
1482 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1483 },
1484 )?;
1485 if registry_dirty
1486 && matches!(
1487 self.config.durability_mode,
1488 crate::api::DurabilityMode::Strict
1489 )
1490 {
1491 self.flush_paged_state()?;
1492 }
1493 Ok(())
1494 }
1495
1496 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1498 self.resident.get_refs_from(id)
1499 }
1500
1501 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1503 self.resident.get_refs_to(id)
1504 }
1505
1506 pub fn expand_refs(
1508 &self,
1509 id: EntityId,
1510 depth: u32,
1511 ref_types: Option<&[RefType]>,
1512 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1513 let mut results = Vec::new();
1514 let mut visited = std::collections::HashSet::new();
1515 visited.insert(id);
1516
1517 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1518
1519 results
1520 }
1521
1522 fn expand_refs_recursive(
1523 &self,
1524 id: EntityId,
1525 max_depth: u32,
1526 ref_types: Option<&[RefType]>,
1527 visited: &mut std::collections::HashSet<EntityId>,
1528 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1529 current_depth: u32,
1530 ) {
1531 if current_depth > max_depth {
1532 return;
1533 }
1534
1535 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1536 if visited.contains(&target_id) {
1537 continue;
1538 }
1539
1540 if let Some(types) = ref_types {
1541 if !types.contains(&ref_type) {
1542 continue;
1543 }
1544 }
1545
1546 visited.insert(target_id);
1547
1548 if let Some(entity) = self.get(&target_collection, target_id) {
1549 results.push((entity, current_depth, ref_type));
1550
1551 self.expand_refs_recursive(
1553 target_id,
1554 max_depth,
1555 ref_types,
1556 visited,
1557 results,
1558 current_depth + 1,
1559 );
1560 }
1561 }
1562 }
1563
1564 pub(crate) fn index_cross_refs(
1566 &self,
1567 entity: &UnifiedEntity,
1568 collection: &str,
1569 ) -> Result<(), StoreError> {
1570 self.resident.index_cross_refs(entity, collection)
1571 }
1572
1573 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1575 self.resident.unindex_cross_refs(id)
1576 }
1577
1578 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1579 self.resident.unindex_cross_refs_batch(ids)
1580 }
1581
1582 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1586 self.resident.unindex_cross_refs_fast_path_hits()
1587 }
1588
1589 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1591 where
1592 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1593 {
1594 self.resident.query_all(filter)
1595 }
1596
1597 pub fn scan<F>(
1599 &self,
1600 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1601 filter: F,
1602 ) -> Vec<(String, UnifiedEntity)>
1603 where
1604 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1605 {
1606 self.resident.scan(snapshot, filter)
1607 }
1608
1609 pub fn filter_metadata_all(
1611 &self,
1612 filters: &[(String, MetadataFilter)],
1613 ) -> Vec<(String, EntityId)> {
1614 self.resident.filter_metadata_all(filters)
1615 }
1616
1617 pub fn stats(&self) -> StoreStats {
1619 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1625 let collections = self.resident.collections.read();
1626 collections
1627 .iter()
1628 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1629 .collect()
1630 };
1631
1632 let mut stats = StoreStats {
1633 collection_count: pairs.len(),
1634 ..Default::default()
1635 };
1636
1637 for (name, manager) in &pairs {
1638 let manager_stats = manager.stats();
1639 stats.total_entities += manager_stats.total_entities;
1640 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1641 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1642 stats.collections.insert(name.clone(), manager_stats);
1643 }
1644
1645 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1646 stats.total_memory_bytes += self
1647 .checkpoint_versions
1648 .read()
1649 .values()
1650 .filter_map(|version| version.deferred_rows.as_ref())
1651 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1652 .sum::<usize>();
1653 stats
1654 }
1655
1656 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1658 let collections = self.resident.collections.read();
1659 for manager in collections.values() {
1660 manager.run_maintenance()?;
1661 }
1662 Ok(())
1663 }
1664
1665 pub fn reclaimable_segment_bytes(&self) -> u64 {
1667 let collections = self.resident.collections.read();
1668 collections
1669 .values()
1670 .map(|manager| manager.reclaimable_bytes())
1671 .fold(0, u64::saturating_add)
1672 }
1673
1674 pub fn pressure_consolidation_tick(&self) -> bool {
1677 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1678 let mut advanced = false;
1681 for manager in &managers {
1682 advanced |= manager.pressure_consolidation_tick();
1683 }
1684 advanced
1685 }
1686}
1687
1688fn flatten_config_json(
1690 prefix: &str,
1691 value: &crate::serde_json::Value,
1692 out: &mut Vec<(String, reddb_types::Value)>,
1693) {
1694 use reddb_types::Value;
1695 match value {
1696 crate::serde_json::Value::Object(map) => {
1697 for (k, v) in map {
1698 let key = if prefix.is_empty() {
1699 k.clone()
1700 } else {
1701 format!("{prefix}.{k}")
1702 };
1703 flatten_config_json(&key, v, out);
1704 }
1705 }
1706 crate::serde_json::Value::String(s) => {
1707 out.push((prefix.to_string(), Value::text(s.clone())));
1708 }
1709 crate::serde_json::Value::Integer(n) => {
1710 out.push((prefix.to_string(), Value::Integer(*n)));
1711 }
1712 crate::serde_json::Value::Number(n) => {
1713 if n.fract().abs() < f64::EPSILON {
1714 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1715 } else {
1716 out.push((prefix.to_string(), Value::Float(*n)));
1717 }
1718 }
1719 crate::serde_json::Value::Decimal(n) => {
1720 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1721 }
1722 crate::serde_json::Value::Bool(b) => {
1723 out.push((prefix.to_string(), Value::Boolean(*b)));
1724 }
1725 crate::serde_json::Value::Null => {
1726 out.push((prefix.to_string(), Value::Null));
1727 }
1728 crate::serde_json::Value::Array(_) => {
1729 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1730 out.push((prefix.to_string(), Value::text(json_str)));
1731 }
1732 }
1733}