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.is_read_only() || (self.pager.is_none() && self.config.embedded_wal_path.is_none())
74 {
75 return Ok(());
76 }
77 let fv = self.format_version();
78 let manager = self
79 .get_collection(collection)
80 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
81 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
82 .iter()
83 .map(|entity| {
84 let metadata = manager.get_metadata(entity.id);
85 (
86 entity.id.raw().to_be_bytes().to_vec(),
87 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
88 )
89 })
90 .collect();
91 serialized.sort_by(|a, b| a.0.cmp(&b.0));
92
93 if !skip_btree
94 && !Self::deferred_store_wal_capture_active()
95 && self.config.resident_memory_bytes_max.is_none()
96 {
97 let Some(pager) = &self.pager else {
98 let records: Vec<Vec<u8>> =
99 serialized.into_iter().map(|(_id, record)| record).collect();
100 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
101 collection: collection.to_string(),
102 records,
103 }])?;
104 return Ok(());
105 };
106 let mut btree_indices = self.btree_indices.write();
107 let btree = btree_indices
108 .entry(collection.to_string())
109 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
110 let root_before = btree.root_page_id();
111 btree.upsert_batch_sorted(&serialized).map_err(|e| {
112 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
113 })?;
114 let root_after = btree.root_page_id();
115 drop(btree_indices);
116 if root_before != root_after {
117 self.mark_paged_registry_dirty();
118 }
119 }
120 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
121 self.finish_paged_write_for_projection(
122 [StoreWalAction::BulkUpsertEntityRecords {
123 collection: collection.to_string(),
124 records,
125 }],
126 skip_btree,
127 )?;
128 Ok(())
129 }
130
131 pub(crate) fn persist_entities_to_pager_wal_only(
143 &self,
144 collection: &str,
145 entities: &[UnifiedEntity],
146 ) -> Result<(), StoreError> {
147 self.persist_entities_impl(collection, entities, true)
148 }
149
150 fn persist_entities_impl(
151 &self,
152 collection: &str,
153 entities: &[UnifiedEntity],
154 skip_btree: bool,
155 ) -> Result<(), StoreError> {
156 let collection = Self::canonical_collection(collection);
157 let skip_btree = skip_btree || self.config.resident_memory_bytes_max.is_some();
158 if entities.is_empty() {
159 return Ok(());
160 }
161
162 if self.is_read_only() || (self.pager.is_none() && self.config.embedded_wal_path.is_none())
163 {
164 return Ok(());
165 }
166
167 let fv = self.format_version();
168 let manager = self
169 .get_collection(collection)
170 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
171 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
172 .iter()
173 .map(|entity| {
174 let metadata = manager.get_metadata(entity.id);
175 (
176 entity.id.raw().to_be_bytes().to_vec(),
177 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
178 )
179 })
180 .collect();
181 serialized.sort_by(|a, b| a.0.cmp(&b.0));
183
184 if !skip_btree
185 && !Self::deferred_store_wal_capture_active()
186 && self.config.resident_memory_bytes_max.is_none()
187 {
188 let Some(pager) = &self.pager else {
189 let records: Vec<Vec<u8>> =
190 serialized.into_iter().map(|(_id, record)| record).collect();
191 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
192 collection: collection.to_string(),
193 records,
194 }])?;
195 return Ok(());
196 };
197 let mut btree_indices = self.btree_indices.write();
198 let btree = btree_indices
199 .entry(collection.to_string())
200 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
201 let root_before = btree.root_page_id();
202
203 btree.upsert_batch_sorted(&serialized).map_err(|e| {
207 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
208 })?;
209 let root_after = btree.root_page_id();
210 drop(btree_indices);
211 if root_before != root_after {
212 self.mark_paged_registry_dirty();
213 }
214 }
215 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
225 self.finish_paged_write_for_projection(
226 [StoreWalAction::BulkUpsertEntityRecords {
227 collection: collection.to_string(),
228 records,
229 }],
230 skip_btree,
231 )?;
232
233 Ok(())
234 }
235
236 pub(crate) fn update_graph_label_index(
238 &self,
239 collection: &str,
240 label: &str,
241 entity_id: EntityId,
242 ) {
243 self.resident
244 .update_graph_label_index(collection, label, entity_id)
245 }
246
247 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
249 self.resident
250 .remove_from_graph_label_index(collection, entity_id)
251 }
252
253 pub(crate) fn remove_from_graph_label_index_batch(
254 &self,
255 collection: &str,
256 entity_ids: &[EntityId],
257 ) {
258 self.resident
259 .remove_from_graph_label_index_batch(collection, entity_ids)
260 }
261
262 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
264 self.resident.lookup_graph_nodes_by_label(label)
265 }
266
267 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
269 self.resident
270 .lookup_graph_nodes_by_label_in(collection, label)
271 }
272
273 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
274 let name = reddb_engine::storage::system_collection_names::canonical_owned(name.into());
275 let _publication = self.vault_publication_lock.lock();
277 let mut collections = self.resident.collections.write();
278
279 if collections.contains_key(&name) {
280 return Err(StoreError::CollectionExists(name));
281 }
282
283 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
284 collections.insert(name.clone(), Arc::new(manager));
285 drop(collections);
286
287 self.mark_paged_registry_dirty();
288 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
289
290 Ok(())
291 }
292
293 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
295 self.resident.get_or_create_collection(
296 reddb_engine::storage::system_collection_names::canonical_owned(name.into()),
297 &self.config.manager_config,
298 )
299 }
300
301 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
303 self.resident
304 .get_collection(Self::canonical_collection(name))
305 }
306
307 pub fn context_index(&self) -> &ContextIndex {
309 self.resident.context_index()
310 }
311
312 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
318 let mut map = self.replayed_turbo_inserts.lock();
319 map.remove(Self::canonical_collection(collection))
320 }
321
322 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
323 let mut deltas = self.replayed_probabilistic_deltas.lock();
324 std::mem::take(&mut *deltas)
325 }
326
327 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
334 self.try_set_config_tree(prefix, json).unwrap_or_default()
337 }
338
339 pub fn try_set_config_tree(
340 &self,
341 prefix: &str,
342 json: &crate::serde_json::Value,
343 ) -> Result<usize, StoreError> {
344 self.try_set_key_value_tree("red_config", prefix, json)
345 }
346
347 pub fn try_set_storage_metadata(
349 &self,
350 key: &str,
351 value: &crate::serde_json::Value,
352 ) -> Result<usize, StoreError> {
353 self.try_set_key_value_tree("red.storage.metadata", key, value)
354 }
355
356 fn try_set_key_value_tree(
357 &self,
358 collection: &str,
359 prefix: &str,
360 json: &crate::serde_json::Value,
361 ) -> Result<usize, StoreError> {
362 let _publication = self.vault_publication_lock.lock();
363 let manager = self.get_or_create_collection(collection);
364 let mut pairs = Vec::new();
365 flatten_config_json(prefix, json, &mut pairs);
366 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
368 .iter()
369 .map(|(key, _)| (key.clone(), Vec::new()))
370 .collect();
371 let mut capture = |entity: &UnifiedEntity| {
372 if let EntityData::Row(row) = &entity.data {
373 if let Some(reddb_types::Value::Text(key)) = row.get_field("key") {
374 if let Some(rows) = previous.get_mut(key.as_ref()) {
375 rows.push((entity.id, row.get_field("value").cloned()));
376 }
377 }
378 }
379 true
380 };
381 if manager.has_backing() {
382 for (key, _) in &pairs {
383 manager.try_for_each_key_entity(key, &mut capture)?;
384 }
385 } else {
386 manager.try_for_each_entity(usize::MAX, &mut capture)?;
387 }
388 let batch_system = collection == "red_config"
389 && prefix == "red.system"
390 && self.config.embedded_wal_path.is_some()
391 && !Self::deferred_store_wal_capture_active();
392 let mut system_rows = Vec::new();
393 let mut saved = 0;
394 for (key, value) in pairs {
395 let stale = previous.remove(&key).unwrap_or_default();
396 if let [(_, Some(current))] = stale.as_slice() {
400 if *current == value {
401 saved += 1;
402 continue;
403 }
404 }
405 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
406 let entity = UnifiedEntity::new(
407 EntityId::new(0),
408 EntityKind::TableRow {
409 table: Arc::from(collection),
410 row_id: 0,
411 },
412 EntityData::Row(RowData {
413 columns: Vec::new(),
414 named: Some(
415 [
416 ("key".to_string(), reddb_types::Value::text(key)),
417 ("value".to_string(), value),
418 ]
419 .into_iter()
420 .collect(),
421 ),
422 schema: None,
423 }),
424 );
425 if batch_system {
426 system_rows.push((entity, stale));
427 saved += 1;
428 continue;
429 }
430 self.insert_auto(collection, entity)?;
433 saved += 1;
434 if !stale.is_empty() {
435 self.delete_batch(collection, &stale)?;
436 }
437 }
438 if !system_rows.is_empty() {
439 self.write_system_config_batch(system_rows)?;
440 }
441 Ok(saved)
442 }
443
444 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
448 self.try_get_config(key).ok().flatten()
451 }
452
453 pub fn try_get_config(&self, key: &str) -> Result<Option<reddb_types::Value>, StoreError> {
454 self.try_get_key_value("red_config", key)
455 }
456
457 pub fn try_get_storage_metadata(
458 &self,
459 key: &str,
460 ) -> Result<Option<reddb_types::Value>, StoreError> {
461 self.try_get_key_value("red.storage.metadata", key)
462 }
463
464 fn try_get_key_value(
465 &self,
466 collection: &str,
467 key: &str,
468 ) -> Result<Option<reddb_types::Value>, StoreError> {
469 let Some(manager) = self.get_collection(collection) else {
470 return Ok(None);
471 };
472 let _publication = manager
473 .has_backing()
474 .then(|| self.vault_publication_lock.lock());
475 let mut latest: Option<(u64, reddb_types::Value)> = None;
476 manager.try_for_each_key_entity(key, |entity| {
477 if let EntityData::Row(row) = &entity.data {
478 let key_matches = matches!(row.get_field("key"), Some(reddb_types::Value::Text(stored)) if stored.as_ref() == key);
479 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
480 if let Some(value) = row.get_field("value") {
481 latest = Some((entity.id.raw(), value.clone()));
482 }
483 }
484 }
485 true
486 })?;
487 Ok(latest.map(|(_, value)| value))
488 }
489
490 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
495 self.resident.set_aux_metadata(bytes)
496 }
497
498 pub(crate) fn persist_aux_metadata(&self, bytes: Vec<u8>) -> Result<(), StoreError> {
501 let bytes = reddb_engine::catalog::codec::canonicalize_collection_contract_blob(&bytes)
502 .map_err(|err| StoreError::Serialization(err.to_string()))?;
503 let _publication = self.vault_publication_lock.lock();
504 self.finish_paged_write([StoreWalAction::SetAuxMetadata {
505 bytes: bytes.clone(),
506 }])?;
507 self.resident.set_aux_metadata(bytes);
508 Ok(())
509 }
510
511 pub fn aux_metadata(&self) -> Vec<u8> {
513 self.resident.aux_metadata()
514 }
515
516 pub fn list_collections(&self) -> Vec<String> {
518 self.resident.list_collections()
519 }
520
521 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
523 let name = Self::canonical_collection(name);
524 let _vault_guard = self.vault_publication_lock.lock();
525 let manager = {
526 let mut collections = self.resident.collections.write();
527
528 collections
529 .remove(name)
530 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
531 };
532
533 let entities = manager.query_all(|_| true);
534 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
535
536 for entity_id in &entity_ids {
537 self.resident.context_index.remove_entity(*entity_id);
538 let _ = self.unindex_cross_refs(*entity_id);
539 }
540
541 if let Some(tree) = self.btree_indices.write().remove(name) {
542 self.retire_btree(tree);
543 }
544 if let Some(index) = self.key_indices.write().remove(name) {
545 for tree in index.trees() {
546 self.retire_btree(Arc::clone(tree));
547 }
548 }
549 self.secondary_index_specs.write().remove(name);
550
551 self.resident
552 .entity_cache
553 .retain(|entity_id, (collection, _)| {
554 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
555 });
556
557 self.resident.cross_refs.write().retain(|source_id, refs| {
558 refs.retain(|(target_id, _, target_collection)| {
559 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
560 });
561 !entity_ids.iter().any(|id| id == source_id)
562 });
563
564 self.resident
565 .reverse_refs
566 .write()
567 .retain(|target_id, refs| {
568 refs.retain(|(source_id, _, source_collection)| {
569 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
570 });
571 !entity_ids.iter().any(|id| id == target_id)
572 });
573
574 self.mark_paged_registry_dirty();
575 self.finish_paged_write([StoreWalAction::DropCollection {
576 name: name.to_string(),
577 }])?;
578
579 Ok(())
580 }
581
582 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
584 let collection = Self::canonical_collection(collection);
585 let manager = self
586 .get_collection(collection)
587 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
588
589 let mut entity = entity;
590 entity.canonicalize_system_collection_names();
591 if entity.id.raw() == 0 {
592 entity.id = self.next_entity_id();
593 } else {
594 self.register_entity_id(entity.id);
595 }
596 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
598 if *row_id == 0 {
599 *row_id = manager.next_row_id();
600 } else {
601 manager.register_row_id(*row_id);
602 }
603 }
604 entity.ensure_table_logical_id();
605 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
607 {
608 Some(node.label.clone())
609 } else {
610 None
611 };
612
613 let id = manager.insert(entity)?;
614 self.register_entity_id(id);
615
616 if let Some(ref label) = graph_node_label {
618 self.update_graph_label_index(collection, label, id);
619 }
620
621 let mut registry_dirty = false;
624 if let Some(pager) = self.pager.as_ref().filter(|_| {
625 !Self::deferred_store_wal_capture_active()
626 && self.config.resident_memory_bytes_max.is_none()
627 }) {
628 if let Some(entity) = manager.get(id) {
629 let mut btree_indices = self.btree_indices.write();
630 let btree = btree_indices
631 .entry(collection.to_string())
632 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
633 let root_before = btree.root_page_id();
634
635 let key = id.raw().to_be_bytes();
636 let metadata = manager.get_metadata(id);
637 let value = Self::serialize_entity_record(
638 &entity,
639 metadata.as_ref(),
640 self.format_version(),
641 );
642 let _ = btree.insert(&key, &value);
644 registry_dirty = root_before != btree.root_page_id();
645 }
646 }
647
648 if self.config.auto_index_refs {
650 if let Some(entity) = manager.get(id) {
651 self.index_cross_refs(&entity, collection)?;
652 }
653 }
654
655 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
659 let actions = manager
660 .get(id)
661 .map(|entity| {
662 let metadata = manager.get_metadata(id);
663 vec![StoreWalAction::upsert_entity(
664 collection,
665 &entity,
666 metadata.as_ref(),
667 self.format_version(),
668 )]
669 })
670 .unwrap_or_default();
671 if registry_dirty {
672 self.mark_paged_registry_dirty();
673 }
674 self.finish_paged_write(actions)?;
675 }
676
677 Ok(id)
678 }
679
680 pub fn bulk_insert(
685 &self,
686 collection: &str,
687 mut entities: Vec<UnifiedEntity>,
688 ) -> Result<Vec<EntityId>, StoreError> {
689 let collection = Self::canonical_collection(collection);
690 for entity in &mut entities {
691 entity.canonicalize_system_collection_names();
692 }
693 let _publication = self.vault_publication_lock.lock();
696 let trace = matches!(
700 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
701 Some("1") | Some("true") | Some("on")
702 );
703 let t_start = std::time::Instant::now();
704 let n = entities.len();
705 let manager = self.get_or_create_collection(collection);
706 let t_get_coll = t_start.elapsed();
707
708 let t0 = std::time::Instant::now();
717 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
718 let n_missing_row_ids = entities
719 .iter()
720 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
721 .count() as u64;
722 let mut entity_id_range = if n_missing_entity_ids > 0 {
723 self.reserve_entity_ids(n_missing_entity_ids)
724 } else {
725 0..0
726 };
727 let mut row_id_range = if n_missing_row_ids > 0 {
728 manager.reserve_row_ids(n_missing_row_ids)
729 } else {
730 0..0
731 };
732 for entity in &mut entities {
733 if entity.id.raw() == 0 {
734 let next = entity_id_range
735 .next()
736 .expect("reserved entity-id range exhausted");
737 entity.id = EntityId::new(next);
738 } else {
739 self.register_entity_id(entity.id);
740 }
741 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
742 if *row_id == 0 {
743 *row_id = row_id_range
744 .next()
745 .expect("reserved row-id range exhausted");
746 } else {
747 manager.register_row_id(*row_id);
748 }
749 }
750 entity.ensure_table_logical_id();
751 }
752 let t_assign_ids = t0.elapsed();
753
754 let graph_labels: Vec<Option<(String, EntityId)>> = entities
756 .iter()
757 .map(|e| {
758 if let EntityKind::GraphNode(ref node) = e.kind {
759 Some((node.label.clone(), e.id))
760 } else {
761 None
762 }
763 })
764 .collect();
765
766 let t0 = std::time::Instant::now();
769 let ids = manager.bulk_insert(entities)?;
770 let t_manager = t0.elapsed();
771 for id in &ids {
772 self.register_entity_id(*id);
773 }
774
775 let t0 = std::time::Instant::now();
782 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
783 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
784 let fv = self.format_version();
785 let serial_map = |id: &EntityId| {
786 manager
787 .get_with(*id, |entity| {
788 (
789 id.raw().to_be_bytes().to_vec(),
790 Self::serialize_entity_record(entity, None, fv),
791 )
792 })
793 .expect("bulk-inserted entity must remain in the fenced segment")
794 };
795 if ids.len() >= 512 {
801 use rayon::prelude::*;
802 Some(ids.par_iter().map(serial_map).collect())
803 } else {
804 Some(ids.iter().map(serial_map).collect())
805 }
806 } else {
807 None
808 };
809 let t_serialize = t0.elapsed();
810
811 for (label, entity_id) in graph_labels.iter().flatten() {
813 self.update_graph_label_index(collection, label, *entity_id);
814 }
815
816 let skip_btree_requested = matches!(
826 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
827 .ok()
828 .as_deref(),
829 Some("1") | Some("true") | Some("on")
830 );
831 let skip_btree = skip_btree_requested && self.pager.is_none();
834 if skip_btree_requested && !skip_btree {
835 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
837 IGNORED.get_or_init(|| {
838 tracing::warn!(
839 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
840 active — flag ignored; bulk inserts will be persisted normally"
841 );
842 });
843 } else if skip_btree {
844 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
846 WARNED.get_or_init(|| {
847 tracing::warn!(
848 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
849 bulk inserts NOT durable; data will be lost on restart"
850 );
851 });
852 }
853
854 let mut t_btree_lock = std::time::Duration::ZERO;
858 let mut t_btree_insert = std::time::Duration::ZERO;
859 let mut t_flush = std::time::Duration::ZERO;
860 if !skip_btree
861 && !Self::deferred_store_wal_capture_active()
862 && self.config.resident_memory_bytes_max.is_none()
863 {
864 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
865 let t0 = std::time::Instant::now();
866 let mut btree_indices = self.btree_indices.write();
867 let btree = btree_indices
868 .entry(collection.to_string())
869 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
870 let root_before = btree.root_page_id();
871 t_btree_lock = t0.elapsed();
872
873 let t0 = std::time::Instant::now();
874 let _ = btree.bulk_insert_sorted(batch);
875 t_btree_insert = t0.elapsed();
876 let registry_dirty = root_before != btree.root_page_id();
877
878 let t0 = std::time::Instant::now();
879 if registry_dirty {
880 self.mark_paged_registry_dirty();
881 }
882 t_flush = t0.elapsed();
883 }
884 }
885
886 let actions = serialized
892 .map(|batch| {
893 let records: Vec<Vec<u8>> =
894 batch.into_iter().map(|(_key, record)| record).collect();
895 vec![StoreWalAction::BulkUpsertEntityRecords {
896 collection: collection.to_string(),
897 records,
898 }]
899 })
900 .unwrap_or_default();
901 self.finish_paged_write(actions)?;
902
903 if trace {
904 tracing::debug!(
905 n,
906 total = ?t_start.elapsed(),
907 get_coll = ?t_get_coll,
908 assign = ?t_assign_ids,
909 serialize = ?t_serialize,
910 manager = ?t_manager,
911 btree_lock = ?t_btree_lock,
912 btree = ?t_btree_insert,
913 flush = ?t_flush,
914 "bulk_insert timing"
915 );
916 }
917
918 Ok(ids)
919 }
920
921 pub fn insert_auto(
923 &self,
924 collection: &str,
925 entity: UnifiedEntity,
926 ) -> Result<EntityId, StoreError> {
927 let collection = Self::canonical_collection(collection);
928 let _publication = self.vault_publication_lock.lock();
930 let manager = self.get_or_create_collection(collection);
931 let mut entity = entity;
932 entity.canonicalize_system_collection_names();
933 if entity.id.raw() == 0 {
934 entity.id = self.next_entity_id();
935 } else {
936 self.register_entity_id(entity.id);
937 }
938 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
940 if *row_id == 0 {
941 *row_id = manager.next_row_id();
942 } else {
943 manager.register_row_id(*row_id);
944 }
945 }
946 entity.ensure_table_logical_id();
947
948 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
950 {
951 Some(node.label.clone())
952 } else {
953 None
954 };
955 self.resident
957 .context_index
958 .index_entity(collection, &entity);
959
960 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
963 if self.config.auto_index_refs {
964 self.index_cross_refs(&entity, collection)?;
965 }
966
967 let paged_btree = if persist_record
974 && !Self::deferred_store_wal_capture_active()
975 && self.config.resident_memory_bytes_max.is_none()
976 {
977 self.get_or_create_btree(collection)
978 } else {
979 None
980 };
981 let persist_fence = paged_btree
982 .as_ref()
983 .map(|_| self.btree_indices.read_recursive());
984 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
985 let unversioned = entity.xmin == 0 && entity.xmax == 0;
986
987 let id = manager.insert(entity)?;
988 let serialized_record = if persist_record {
989 Some(
990 manager
991 .get_with(id, |entity| {
992 Self::serialize_entity_record(entity, None, self.format_version())
993 })
994 .ok_or(StoreError::EntityNotFound(id))?,
995 )
996 } else {
997 None
998 };
999 if let Some(ref label) = graph_node_label {
1007 self.update_graph_label_index(collection, label, id);
1008 }
1009
1010 let mut registry_dirty = false;
1011 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
1012 let root_before = btree.root_page_id();
1013
1014 let key = id.raw().to_be_bytes();
1015 btree.insert(&key, record).map_err(|e| {
1016 StoreError::Io(std::io::Error::other(format!(
1017 "B-tree insert error while inserting '{collection}'/{id}: {e}"
1018 )))
1019 })?;
1020 registry_dirty = root_before != btree.root_page_id();
1021 }
1022 drop(persist_fence);
1023
1024 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
1028 let actions = serialized_record
1029 .map(|record| {
1030 vec![StoreWalAction::UpsertEntityRecord {
1031 collection: collection.to_string(),
1032 record,
1033 }]
1034 })
1035 .unwrap_or_default();
1036 if registry_dirty {
1037 self.mark_paged_registry_dirty();
1038 }
1039 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1040 drop(_publication);
1041 self.complete_grouped_actions(&pending, &actions)?;
1042 } else {
1043 self.finish_paged_write(actions)?;
1044 }
1045 }
1046 if unversioned && !Self::deferred_store_wal_capture_active() {
1047 if let Some(before) = before {
1048 self.note_paged_insert(collection, &manager, &before, id);
1049 }
1050 }
1051 Ok(id)
1052 }
1053
1054 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
1060 self.try_get(collection, id, usize::MAX).ok().flatten()
1063 }
1064
1065 pub fn try_get(
1066 &self,
1067 collection: &str,
1068 id: EntityId,
1069 limit_bytes: usize,
1070 ) -> Result<Option<UnifiedEntity>, StoreError> {
1071 let Some(manager) = self.get_collection(collection) else {
1072 return Ok(None);
1073 };
1074 manager.try_get(id, limit_bytes)
1075 }
1076
1077 pub fn get_table_row_by_logical_id(
1083 &self,
1084 collection: &str,
1085 logical_id: EntityId,
1086 ) -> Option<UnifiedEntity> {
1087 if let Some(entity) = self.get(collection, logical_id) {
1088 if matches!(entity.kind, EntityKind::TableRow { .. })
1089 && entity.logical_id() == logical_id
1090 && entity.xmax == 0
1091 {
1092 return Some(entity);
1093 }
1094 }
1095
1096 let manager = self.get_collection(collection)?;
1097 let mut matches = manager.query_all(|entity| {
1098 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1099 });
1100 matches
1101 .iter()
1102 .find(|entity| entity.xmax == 0)
1103 .cloned()
1104 .or_else(|| matches.pop())
1105 }
1106
1107 pub fn table_row_versions_by_logical_id(
1108 &self,
1109 collection: &str,
1110 logical_id: EntityId,
1111 ) -> Vec<UnifiedEntity> {
1112 self.resident
1113 .table_row_versions_by_logical_id(collection, logical_id)
1114 }
1115
1116 pub(crate) fn try_for_each_table_row_version(
1117 &self,
1118 collection: &str,
1119 logical_id: EntityId,
1120 mut visit: impl FnMut(&UnifiedEntity) -> bool,
1121 ) -> Result<(), StoreError> {
1122 let Some(manager) = self.get_collection(collection) else {
1123 return Ok(());
1124 };
1125 manager.try_for_each_entity(usize::MAX, |entity| {
1126 !matches!(entity.kind, EntityKind::TableRow { .. })
1127 || entity.logical_id() != logical_id
1128 || visit(entity)
1129 })
1130 }
1131
1132 pub(crate) fn try_get_table_row_by_logical_id(
1133 &self,
1134 collection: &str,
1135 logical_id: EntityId,
1136 ) -> Result<Option<UnifiedEntity>, StoreError> {
1137 let Some(manager) = self.get_collection(collection) else {
1138 return Ok(None);
1139 };
1140 if let Some(entity) = manager.try_get(logical_id, usize::MAX)? {
1141 if matches!(entity.kind, EntityKind::TableRow { .. })
1142 && entity.logical_id() == logical_id
1143 && entity.xmax == 0
1144 {
1145 return Ok(Some(entity));
1146 }
1147 }
1148 let mut selected = None;
1151 self.try_for_each_table_row_version(collection, logical_id, |entity| {
1152 selected = Some(entity.clone());
1153 entity.xmax != 0
1154 })?;
1155 manager.restore_owned_row_schemas(selected.iter_mut());
1156 Ok(selected)
1157 }
1158
1159 pub fn vacuum_mvcc_history(
1160 &self,
1161 collection: &str,
1162 cutoff_xid: u64,
1163 ) -> Result<MvccVacuumStats, StoreError> {
1164 let manager = self
1165 .get_collection(collection)
1166 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1167 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1168 manager.for_each_entity(|entity| {
1169 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1170 return true;
1171 }
1172 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1173 if entity.xmax == 0 {
1174 entry.0 = true;
1175 }
1176 entry.1 = entry.1.max(entity.xmin);
1177 true
1178 });
1179
1180 let mut stats = MvccVacuumStats::default();
1181 let mut reclaim_ids = Vec::new();
1182 manager.for_each_entity(|entity| {
1183 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1184 return true;
1185 }
1186 stats.scanned_versions += 1;
1187 let (has_live_version, max_xmin) = logical
1188 .get(&entity.logical_id())
1189 .copied()
1190 .unwrap_or((false, entity.xmin));
1191 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1192 if entity.xmax < cutoff_xid {
1193 stats.reclaimed_versions += 1;
1194 if is_delete_tombstone {
1195 stats.reclaimed_tombstones += 1;
1196 } else {
1197 stats.reclaimed_history_versions += 1;
1198 }
1199 reclaim_ids.push(entity.id);
1200 } else {
1201 stats.retained_versions += 1;
1202 if is_delete_tombstone {
1203 stats.retained_tombstones += 1;
1204 } else {
1205 stats.retained_history_versions += 1;
1206 }
1207 }
1208 true
1209 });
1210
1211 if !reclaim_ids.is_empty() {
1212 self.delete_batch(collection, &reclaim_ids)?;
1213 }
1214 Ok(stats)
1215 }
1216
1217 pub(crate) fn install_versioned_table_row_update(
1218 &self,
1219 collection: &str,
1220 old_version: UnifiedEntity,
1221 mut new_version: UnifiedEntity,
1222 metadata: Option<&Metadata>,
1223 ) -> Result<(), StoreError> {
1224 let manager = self
1225 .get_collection(collection)
1226 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1227
1228 let old_id = old_version.id;
1229 let new_id = new_version.id;
1230 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1231
1232 self.resident.entity_cache.remove(old_id.raw());
1233 self.resident.entity_cache.remove(new_id.raw());
1234 manager.update(old_version.clone())?;
1235 self.resident.context_index.remove_entity(old_id);
1236
1237 self.register_entity_id(new_version.id);
1238 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1239 if *row_id == 0 {
1240 *row_id = manager.next_row_id();
1241 } else {
1242 manager.register_row_id(*row_id);
1243 }
1244 }
1245 new_version.ensure_table_logical_id();
1246 manager.insert(new_version.clone())?;
1247 if let Some(metadata) = inherited_metadata {
1248 manager.set_metadata(new_id, metadata)?;
1249 }
1250 self.resident
1251 .context_index
1252 .index_entity(collection, &new_version);
1253 if self.config.auto_index_refs {
1254 self.index_cross_refs(&new_version, collection)?;
1255 }
1256
1257 let old_metadata = manager.get_metadata(old_id);
1258 let new_metadata = manager.get_metadata(new_id);
1259 let fv = self.format_version();
1260 let records = vec![
1263 manager
1264 .get_with(old_id, |entity| {
1265 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1266 })
1267 .ok_or(StoreError::EntityNotFound(old_id))?,
1268 manager
1269 .get_with(new_id, |entity| {
1270 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1271 })
1272 .ok_or(StoreError::EntityNotFound(new_id))?,
1273 ];
1274 self.finish_paged_write_for_projection(
1275 [StoreWalAction::BulkUpsertEntityRecords {
1276 collection: collection.to_string(),
1277 records,
1278 }],
1279 true,
1280 )?;
1281
1282 Ok(())
1283 }
1284
1285 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1291 match self.get_collection(collection) {
1292 Some(manager) => manager.get_many(ids),
1293 None => vec![None; ids.len()],
1294 }
1295 }
1296
1297 pub fn try_get_batch(
1299 &self,
1300 collection: &str,
1301 ids: &[EntityId],
1302 read_bytes_max: usize,
1303 ) -> Result<Vec<Option<UnifiedEntity>>, StoreError> {
1304 match self.get_collection(collection) {
1305 Some(manager) => manager.try_get_many(ids, read_bytes_max),
1306 None => Ok(vec![None; ids.len()]),
1307 }
1308 }
1309
1310 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1312 self.resident.get_any(id)
1313 }
1314
1315 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1321 self.resident.entity_cache_hit_rate()
1322 }
1323
1324 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1326 self.resident.entity_cache_stats()
1327 }
1328
1329 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1331 let collection = Self::canonical_collection(collection);
1332 let publication = self.vault_publication_lock.lock();
1333 self.resident.entity_cache.remove(id.raw());
1335 let manager = self
1336 .get_collection(collection)
1337 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1338
1339 let deleted = manager.delete(id)?;
1340 if !deleted {
1341 return Ok(false);
1342 }
1343
1344 let mut registry_dirty = false;
1346 if self.pager.is_some()
1347 && !self.is_read_only()
1348 && self.config.resident_memory_bytes_max.is_none()
1349 {
1350 let btree_indices = self.btree_indices.read();
1351 if let Some(btree) = btree_indices.get(collection) {
1352 let root_before = btree.root_page_id();
1353 let key = id.raw().to_be_bytes();
1354 let _ = btree.delete(&key);
1355 registry_dirty = root_before != btree.root_page_id();
1356 }
1357 }
1358
1359 self.unindex_cross_refs(id)?;
1361
1362 self.remove_from_graph_label_index(collection, id);
1364
1365 if registry_dirty {
1366 self.mark_paged_registry_dirty();
1367 }
1368 let actions = [StoreWalAction::DeleteEntityRecord {
1369 collection: collection.to_string(),
1370 entity_id: id.raw(),
1371 }];
1372 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1373 drop(publication);
1374 self.complete_grouped_actions(&pending, &actions)?;
1375 } else {
1376 self.finish_paged_write(actions)?;
1377 }
1378
1379 Ok(true)
1380 }
1381
1382 pub fn delete_batch(
1383 &self,
1384 collection: &str,
1385 ids: &[EntityId],
1386 ) -> Result<Vec<EntityId>, StoreError> {
1387 let collection = Self::canonical_collection(collection);
1388 if ids.is_empty() {
1389 return Ok(Vec::new());
1390 }
1391 let _publication = self.vault_publication_lock.lock();
1394
1395 self.resident
1400 .entity_cache
1401 .remove_many(ids.iter().map(|id| id.raw()));
1402
1403 let manager = self
1404 .get_collection(collection)
1405 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1406
1407 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1408 let deleted_ids = manager.delete_batch(ids)?;
1409 if deleted_ids.is_empty() {
1410 return Ok(deleted_ids);
1411 }
1412
1413 let mut registry_dirty = false;
1414 let mut updated_tree = false;
1415 if self.pager.is_some()
1416 && !self.is_read_only()
1417 && self.config.resident_memory_bytes_max.is_none()
1418 {
1419 let btree_indices = self.btree_indices.read();
1420 if let Some(btree) = btree_indices.get(collection) {
1421 updated_tree = true;
1422 let root_before = btree.root_page_id();
1423 for id in &deleted_ids {
1424 let key = id.raw().to_be_bytes();
1425 updated_tree &= btree.delete(&key).is_ok();
1428 }
1429 registry_dirty = root_before != btree.root_page_id();
1430 }
1431 }
1432
1433 self.unindex_cross_refs_batch(&deleted_ids)?;
1434 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1435 if registry_dirty {
1436 self.mark_paged_registry_dirty();
1437 }
1438 let actions = deleted_ids
1439 .iter()
1440 .map(|id| StoreWalAction::DeleteEntityRecord {
1441 collection: collection.to_string(),
1442 entity_id: id.raw(),
1443 })
1444 .collect::<Vec<_>>();
1445 self.finish_paged_write(actions)?;
1446
1447 if updated_tree {
1448 if let Some(before) = before {
1449 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1450 }
1451 self.flush_paged_registry()?;
1456 }
1457 Ok(deleted_ids)
1458 }
1459
1460 pub fn set_metadata(
1462 &self,
1463 collection: &str,
1464 id: EntityId,
1465 metadata: Metadata,
1466 ) -> Result<(), StoreError> {
1467 let manager = self
1468 .get_collection(collection)
1469 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1470
1471 manager.set_metadata(id, metadata)?;
1472 if let Some(entity) = manager.get(id) {
1473 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1474 }
1475 Ok(())
1476 }
1477
1478 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1480 self.get_collection(collection)?.get_metadata(id)
1481 }
1482
1483 pub fn try_get_metadata(
1485 &self,
1486 collection: &str,
1487 id: EntityId,
1488 limit_bytes: usize,
1489 ) -> Result<Option<Metadata>, StoreError> {
1490 let Some(manager) = self.get_collection(collection) else {
1491 return Ok(None);
1492 };
1493 manager.try_get_metadata(id, limit_bytes)
1494 }
1495
1496 pub fn add_cross_ref(
1498 &self,
1499 source_collection: &str,
1500 source_id: EntityId,
1501 target_collection: &str,
1502 target_id: EntityId,
1503 ref_type: RefType,
1504 weight: f32,
1505 ) -> Result<(), StoreError> {
1506 let source_collection = Self::canonical_collection(source_collection);
1507 let target_collection = Self::canonical_collection(target_collection);
1508 let registry_dirty = self.resident.add_cross_ref_in_memory(
1509 source_collection,
1510 source_id,
1511 target_collection,
1512 target_id,
1513 ref_type,
1514 weight,
1515 self.config.max_cross_refs,
1516 |entity| {
1517 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1518 },
1519 )?;
1520 if registry_dirty
1521 && matches!(
1522 self.config.durability_mode,
1523 crate::api::DurabilityMode::Strict
1524 )
1525 {
1526 self.flush_paged_state()?;
1527 }
1528 Ok(())
1529 }
1530
1531 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1533 self.resident.get_refs_from(id)
1534 }
1535
1536 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1538 self.resident.get_refs_to(id)
1539 }
1540
1541 pub fn expand_refs(
1543 &self,
1544 id: EntityId,
1545 depth: u32,
1546 ref_types: Option<&[RefType]>,
1547 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1548 let mut results = Vec::new();
1549 let mut visited = std::collections::HashSet::new();
1550 visited.insert(id);
1551
1552 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1553
1554 results
1555 }
1556
1557 fn expand_refs_recursive(
1558 &self,
1559 id: EntityId,
1560 max_depth: u32,
1561 ref_types: Option<&[RefType]>,
1562 visited: &mut std::collections::HashSet<EntityId>,
1563 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1564 current_depth: u32,
1565 ) {
1566 if current_depth > max_depth {
1567 return;
1568 }
1569
1570 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1571 if visited.contains(&target_id) {
1572 continue;
1573 }
1574
1575 if let Some(types) = ref_types {
1576 if !types.contains(&ref_type) {
1577 continue;
1578 }
1579 }
1580
1581 visited.insert(target_id);
1582
1583 if let Some(entity) = self.get(&target_collection, target_id) {
1584 results.push((entity, current_depth, ref_type));
1585
1586 self.expand_refs_recursive(
1588 target_id,
1589 max_depth,
1590 ref_types,
1591 visited,
1592 results,
1593 current_depth + 1,
1594 );
1595 }
1596 }
1597 }
1598
1599 pub(crate) fn index_cross_refs(
1601 &self,
1602 entity: &UnifiedEntity,
1603 collection: &str,
1604 ) -> Result<(), StoreError> {
1605 self.resident.index_cross_refs(entity, collection)
1606 }
1607
1608 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1610 self.resident.unindex_cross_refs(id)
1611 }
1612
1613 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1614 self.resident.unindex_cross_refs_batch(ids)
1615 }
1616
1617 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1621 self.resident.unindex_cross_refs_fast_path_hits()
1622 }
1623
1624 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1626 where
1627 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1628 {
1629 self.resident.query_all(filter)
1630 }
1631
1632 pub fn scan<F>(
1634 &self,
1635 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1636 filter: F,
1637 ) -> Vec<(String, UnifiedEntity)>
1638 where
1639 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1640 {
1641 self.resident.scan(snapshot, filter)
1642 }
1643
1644 pub fn filter_metadata_all(
1646 &self,
1647 filters: &[(String, MetadataFilter)],
1648 ) -> Vec<(String, EntityId)> {
1649 self.resident.filter_metadata_all(filters)
1650 }
1651
1652 pub fn stats(&self) -> StoreStats {
1654 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1660 let collections = self.resident.collections.read();
1661 collections
1662 .iter()
1663 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1664 .collect()
1665 };
1666
1667 let mut stats = StoreStats {
1668 collection_count: pairs.len(),
1669 ..Default::default()
1670 };
1671
1672 for (name, manager) in &pairs {
1673 let manager_stats = manager.stats();
1674 stats.total_entities += manager_stats.total_entities;
1675 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1676 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1677 stats.collections.insert(name.clone(), manager_stats);
1678 }
1679
1680 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1681 stats.total_memory_bytes += self
1682 .checkpoint_versions
1683 .read()
1684 .values()
1685 .filter_map(|version| version.deferred_rows.as_ref())
1686 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1687 .sum::<usize>();
1688 stats
1689 }
1690
1691 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1693 let collections = self.resident.collections.read();
1694 for manager in collections.values() {
1695 manager.run_maintenance()?;
1696 }
1697 Ok(())
1698 }
1699
1700 pub fn reclaimable_segment_bytes(&self) -> u64 {
1702 let collections = self.resident.collections.read();
1703 collections
1704 .values()
1705 .map(|manager| manager.reclaimable_bytes())
1706 .fold(0, u64::saturating_add)
1707 }
1708
1709 pub fn pressure_consolidation_tick(&self) -> bool {
1712 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1713 let mut advanced = false;
1716 for manager in &managers {
1717 advanced |= manager.pressure_consolidation_tick();
1718 }
1719 advanced
1720 }
1721}
1722
1723fn flatten_config_json(
1725 prefix: &str,
1726 value: &crate::serde_json::Value,
1727 out: &mut Vec<(String, reddb_types::Value)>,
1728) {
1729 use reddb_types::Value;
1730 match value {
1731 crate::serde_json::Value::Object(map) => {
1732 for (k, v) in map {
1733 let key = if prefix.is_empty() {
1734 k.clone()
1735 } else {
1736 format!("{prefix}.{k}")
1737 };
1738 flatten_config_json(&key, v, out);
1739 }
1740 }
1741 crate::serde_json::Value::String(s) => {
1742 out.push((prefix.to_string(), Value::text(s.clone())));
1743 }
1744 crate::serde_json::Value::Integer(n) => {
1745 out.push((prefix.to_string(), Value::Integer(*n)));
1746 }
1747 crate::serde_json::Value::Number(n) => {
1748 if n.fract().abs() < f64::EPSILON {
1749 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1750 } else {
1751 out.push((prefix.to_string(), Value::Float(*n)));
1752 }
1753 }
1754 crate::serde_json::Value::Decimal(n) => {
1755 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1756 }
1757 crate::serde_json::Value::Bool(b) => {
1758 out.push((prefix.to_string(), Value::Boolean(*b)));
1759 }
1760 crate::serde_json::Value::Null => {
1761 out.push((prefix.to_string(), Value::Null));
1762 }
1763 crate::serde_json::Value::Array(_) => {
1764 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1765 out.push((prefix.to_string(), Value::text(json_str)));
1766 }
1767 }
1768}