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 _publication = self.vault_publication_lock.lock();
276 let mut collections = self.resident.collections.write();
277
278 if collections.contains_key(&name) {
279 return Err(StoreError::CollectionExists(name));
280 }
281
282 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
283 collections.insert(name.clone(), Arc::new(manager));
284 drop(collections);
285
286 let create = match self.prepare_operational_collection_create(&name) {
287 Ok(create) => create,
288 Err(err) => {
289 let mut collections = self.resident.collections.write();
290 collections.remove(&name);
291 return Err(err);
292 }
293 };
294 if let Some(create) = create {
295 if let Err(err) = create.publish().map_err(StoreError::Io) {
296 let mut collections = self.resident.collections.write();
297 collections.remove(&name);
298 return Err(err);
299 }
300 }
301
302 self.mark_paged_registry_dirty();
303 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
304
305 Ok(())
306 }
307
308 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
310 self.resident.get_or_create_collection(
311 reddb_engine::storage::system_collection_names::canonical_owned(name.into()),
312 &self.config.manager_config,
313 )
314 }
315
316 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
318 self.resident
319 .get_collection(Self::canonical_collection(name))
320 }
321
322 pub fn context_index(&self) -> &ContextIndex {
324 self.resident.context_index()
325 }
326
327 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
333 let mut map = self.replayed_turbo_inserts.lock();
334 map.remove(Self::canonical_collection(collection))
335 }
336
337 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
338 let mut deltas = self.replayed_probabilistic_deltas.lock();
339 std::mem::take(&mut *deltas)
340 }
341
342 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
349 self.try_set_config_tree(prefix, json).unwrap_or_default()
352 }
353
354 pub fn try_set_config_tree(
355 &self,
356 prefix: &str,
357 json: &crate::serde_json::Value,
358 ) -> Result<usize, StoreError> {
359 let _publication = self.vault_publication_lock.lock();
360 let manager = self.get_or_create_collection("red_config");
361 let mut pairs = Vec::new();
362 flatten_config_json(prefix, json, &mut pairs);
363 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
365 .iter()
366 .map(|(key, _)| (key.clone(), Vec::new()))
367 .collect();
368 let mut capture = |entity: &UnifiedEntity| {
369 if let EntityData::Row(row) = &entity.data {
370 if let Some(reddb_types::Value::Text(key)) = row.get_field("key") {
371 if let Some(rows) = previous.get_mut(key.as_ref()) {
372 rows.push((entity.id, row.get_field("value").cloned()));
373 }
374 }
375 }
376 true
377 };
378 if manager.has_backing() {
379 for (key, _) in &pairs {
380 manager.try_for_each_key_entity(key, &mut capture)?;
381 }
382 } else {
383 manager.try_for_each_entity(usize::MAX, &mut capture)?;
384 }
385 let mut saved = 0;
386 for (key, value) in pairs {
387 let stale = previous.remove(&key).unwrap_or_default();
388 if let [(_, Some(current))] = stale.as_slice() {
392 if *current == value {
393 saved += 1;
394 continue;
395 }
396 }
397 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
398 let entity = UnifiedEntity::new(
402 EntityId::new(0),
403 EntityKind::TableRow {
404 table: Arc::from("red_config"),
405 row_id: 0,
406 },
407 EntityData::Row(RowData {
408 columns: Vec::new(),
409 named: Some(
410 [
411 ("key".to_string(), reddb_types::Value::text(key)),
412 ("value".to_string(), value),
413 ]
414 .into_iter()
415 .collect(),
416 ),
417 schema: None,
418 }),
419 );
420 self.insert_auto("red_config", entity)?;
421 saved += 1;
422 if !stale.is_empty() {
423 self.delete_batch("red_config", &stale)?;
424 }
425 }
426 Ok(saved)
427 }
428
429 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
433 self.try_get_config(key).ok().flatten()
436 }
437
438 pub fn try_get_config(&self, key: &str) -> Result<Option<reddb_types::Value>, StoreError> {
439 let Some(manager) = self.get_collection("red_config") else {
440 return Ok(None);
441 };
442 let _publication = manager
443 .has_backing()
444 .then(|| self.vault_publication_lock.lock());
445 let mut latest: Option<(u64, reddb_types::Value)> = None;
446 manager.try_for_each_key_entity(key, |entity| {
447 if let EntityData::Row(row) = &entity.data {
448 let key_matches = matches!(row.get_field("key"), Some(reddb_types::Value::Text(stored)) if stored.as_ref() == key);
449 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
450 if let Some(value) = row.get_field("value") {
451 latest = Some((entity.id.raw(), value.clone()));
452 }
453 }
454 }
455 true
456 })?;
457 Ok(latest.map(|(_, value)| value))
458 }
459
460 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
465 self.resident.set_aux_metadata(bytes)
466 }
467
468 pub(crate) fn persist_aux_metadata(&self, bytes: Vec<u8>) -> Result<(), StoreError> {
471 let bytes = reddb_engine::catalog::codec::canonicalize_collection_contract_blob(&bytes)
472 .map_err(|err| StoreError::Serialization(err.to_string()))?;
473 let _publication = self.vault_publication_lock.lock();
474 self.finish_paged_write([StoreWalAction::SetAuxMetadata {
475 bytes: bytes.clone(),
476 }])?;
477 self.resident.set_aux_metadata(bytes);
478 Ok(())
479 }
480
481 pub fn aux_metadata(&self) -> Vec<u8> {
483 self.resident.aux_metadata()
484 }
485
486 pub fn list_collections(&self) -> Vec<String> {
488 self.resident.list_collections()
489 }
490
491 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
493 let name = Self::canonical_collection(name);
494 let _vault_guard = self.vault_publication_lock.lock();
495 let drop = self.prepare_operational_collection_drop(name)?;
496 let manager = {
497 let mut collections = self.resident.collections.write();
498
499 collections
500 .remove(name)
501 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
502 };
503
504 let entities = manager.query_all(|_| true);
505 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
506
507 for entity_id in &entity_ids {
508 self.resident.context_index.remove_entity(*entity_id);
509 let _ = self.unindex_cross_refs(*entity_id);
510 }
511
512 if let Some(tree) = self.btree_indices.write().remove(name) {
513 self.retire_btree(tree);
514 }
515 if let Some(index) = self.key_indices.write().remove(name) {
516 for tree in index.trees() {
517 self.retire_btree(Arc::clone(tree));
518 }
519 }
520 self.secondary_index_specs.write().remove(name);
521
522 self.resident
523 .entity_cache
524 .retain(|entity_id, (collection, _)| {
525 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
526 });
527
528 self.resident.cross_refs.write().retain(|source_id, refs| {
529 refs.retain(|(target_id, _, target_collection)| {
530 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
531 });
532 !entity_ids.iter().any(|id| id == source_id)
533 });
534
535 self.resident
536 .reverse_refs
537 .write()
538 .retain(|target_id, refs| {
539 refs.retain(|(source_id, _, source_collection)| {
540 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
541 });
542 !entity_ids.iter().any(|id| id == target_id)
543 });
544
545 self.mark_paged_registry_dirty();
546 self.finish_paged_write([StoreWalAction::DropCollection {
547 name: name.to_string(),
548 }])?;
549 if let Some(drop) = drop {
550 drop.finish().map_err(StoreError::Io)?;
551 }
552
553 Ok(())
554 }
555
556 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
558 let collection = Self::canonical_collection(collection);
559 let manager = self
560 .get_collection(collection)
561 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
562
563 let mut entity = entity;
564 entity.canonicalize_system_collection_names();
565 if entity.id.raw() == 0 {
566 entity.id = self.next_entity_id();
567 } else {
568 self.register_entity_id(entity.id);
569 }
570 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
572 if *row_id == 0 {
573 *row_id = manager.next_row_id();
574 } else {
575 manager.register_row_id(*row_id);
576 }
577 }
578 entity.ensure_table_logical_id();
579 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
581 {
582 Some(node.label.clone())
583 } else {
584 None
585 };
586
587 let id = manager.insert(entity)?;
588 self.register_entity_id(id);
589
590 if let Some(ref label) = graph_node_label {
592 self.update_graph_label_index(collection, label, id);
593 }
594
595 let mut registry_dirty = false;
598 if let Some(pager) = self.pager.as_ref().filter(|_| {
599 !Self::deferred_store_wal_capture_active()
600 && self.config.resident_memory_bytes_max.is_none()
601 }) {
602 if let Some(entity) = manager.get(id) {
603 let mut btree_indices = self.btree_indices.write();
604 let btree = btree_indices
605 .entry(collection.to_string())
606 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
607 let root_before = btree.root_page_id();
608
609 let key = id.raw().to_be_bytes();
610 let metadata = manager.get_metadata(id);
611 let value = Self::serialize_entity_record(
612 &entity,
613 metadata.as_ref(),
614 self.format_version(),
615 );
616 let _ = btree.insert(&key, &value);
618 registry_dirty = root_before != btree.root_page_id();
619 }
620 }
621
622 if self.config.auto_index_refs {
624 if let Some(entity) = manager.get(id) {
625 self.index_cross_refs(&entity, collection)?;
626 }
627 }
628
629 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
633 let actions = manager
634 .get(id)
635 .map(|entity| {
636 let metadata = manager.get_metadata(id);
637 vec![StoreWalAction::upsert_entity(
638 collection,
639 &entity,
640 metadata.as_ref(),
641 self.format_version(),
642 )]
643 })
644 .unwrap_or_default();
645 if registry_dirty {
646 self.mark_paged_registry_dirty();
647 }
648 self.finish_paged_write(actions)?;
649 }
650
651 Ok(id)
652 }
653
654 pub fn bulk_insert(
659 &self,
660 collection: &str,
661 mut entities: Vec<UnifiedEntity>,
662 ) -> Result<Vec<EntityId>, StoreError> {
663 let collection = Self::canonical_collection(collection);
664 for entity in &mut entities {
665 entity.canonicalize_system_collection_names();
666 }
667 let _publication = self.vault_publication_lock.lock();
670 let trace = matches!(
674 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
675 Some("1") | Some("true") | Some("on")
676 );
677 let t_start = std::time::Instant::now();
678 let n = entities.len();
679 let manager = self.get_or_create_collection(collection);
680 let t_get_coll = t_start.elapsed();
681
682 let t0 = std::time::Instant::now();
691 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
692 let n_missing_row_ids = entities
693 .iter()
694 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
695 .count() as u64;
696 let mut entity_id_range = if n_missing_entity_ids > 0 {
697 self.reserve_entity_ids(n_missing_entity_ids)
698 } else {
699 0..0
700 };
701 let mut row_id_range = if n_missing_row_ids > 0 {
702 manager.reserve_row_ids(n_missing_row_ids)
703 } else {
704 0..0
705 };
706 for entity in &mut entities {
707 if entity.id.raw() == 0 {
708 let next = entity_id_range
709 .next()
710 .expect("reserved entity-id range exhausted");
711 entity.id = EntityId::new(next);
712 } else {
713 self.register_entity_id(entity.id);
714 }
715 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
716 if *row_id == 0 {
717 *row_id = row_id_range
718 .next()
719 .expect("reserved row-id range exhausted");
720 } else {
721 manager.register_row_id(*row_id);
722 }
723 }
724 entity.ensure_table_logical_id();
725 }
726 let t_assign_ids = t0.elapsed();
727
728 let graph_labels: Vec<Option<(String, EntityId)>> = entities
730 .iter()
731 .map(|e| {
732 if let EntityKind::GraphNode(ref node) = e.kind {
733 Some((node.label.clone(), e.id))
734 } else {
735 None
736 }
737 })
738 .collect();
739
740 let t0 = std::time::Instant::now();
743 let ids = manager.bulk_insert(entities)?;
744 let t_manager = t0.elapsed();
745 for id in &ids {
746 self.register_entity_id(*id);
747 }
748
749 let t0 = std::time::Instant::now();
756 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
757 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
758 let fv = self.format_version();
759 let serial_map = |id: &EntityId| {
760 manager
761 .get_with(*id, |entity| {
762 (
763 id.raw().to_be_bytes().to_vec(),
764 Self::serialize_entity_record(entity, None, fv),
765 )
766 })
767 .expect("bulk-inserted entity must remain in the fenced segment")
768 };
769 if ids.len() >= 512 {
775 use rayon::prelude::*;
776 Some(ids.par_iter().map(serial_map).collect())
777 } else {
778 Some(ids.iter().map(serial_map).collect())
779 }
780 } else {
781 None
782 };
783 let t_serialize = t0.elapsed();
784
785 for (label, entity_id) in graph_labels.iter().flatten() {
787 self.update_graph_label_index(collection, label, *entity_id);
788 }
789
790 let skip_btree_requested = matches!(
800 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
801 .ok()
802 .as_deref(),
803 Some("1") | Some("true") | Some("on")
804 );
805 let skip_btree = skip_btree_requested && self.pager.is_none();
808 if skip_btree_requested && !skip_btree {
809 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
811 IGNORED.get_or_init(|| {
812 tracing::warn!(
813 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
814 active — flag ignored; bulk inserts will be persisted normally"
815 );
816 });
817 } else if skip_btree {
818 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
820 WARNED.get_or_init(|| {
821 tracing::warn!(
822 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
823 bulk inserts NOT durable; data will be lost on restart"
824 );
825 });
826 }
827
828 let mut t_btree_lock = std::time::Duration::ZERO;
832 let mut t_btree_insert = std::time::Duration::ZERO;
833 let mut t_flush = std::time::Duration::ZERO;
834 if !skip_btree
835 && !Self::deferred_store_wal_capture_active()
836 && self.config.resident_memory_bytes_max.is_none()
837 {
838 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
839 let t0 = std::time::Instant::now();
840 let mut btree_indices = self.btree_indices.write();
841 let btree = btree_indices
842 .entry(collection.to_string())
843 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
844 let root_before = btree.root_page_id();
845 t_btree_lock = t0.elapsed();
846
847 let t0 = std::time::Instant::now();
848 let _ = btree.bulk_insert_sorted(batch);
849 t_btree_insert = t0.elapsed();
850 let registry_dirty = root_before != btree.root_page_id();
851
852 let t0 = std::time::Instant::now();
853 if registry_dirty {
854 self.mark_paged_registry_dirty();
855 }
856 t_flush = t0.elapsed();
857 }
858 }
859
860 let actions = serialized
866 .map(|batch| {
867 let records: Vec<Vec<u8>> =
868 batch.into_iter().map(|(_key, record)| record).collect();
869 vec![StoreWalAction::BulkUpsertEntityRecords {
870 collection: collection.to_string(),
871 records,
872 }]
873 })
874 .unwrap_or_default();
875 self.finish_paged_write(actions)?;
876
877 if trace {
878 tracing::debug!(
879 n,
880 total = ?t_start.elapsed(),
881 get_coll = ?t_get_coll,
882 assign = ?t_assign_ids,
883 serialize = ?t_serialize,
884 manager = ?t_manager,
885 btree_lock = ?t_btree_lock,
886 btree = ?t_btree_insert,
887 flush = ?t_flush,
888 "bulk_insert timing"
889 );
890 }
891
892 Ok(ids)
893 }
894
895 pub fn insert_auto(
897 &self,
898 collection: &str,
899 entity: UnifiedEntity,
900 ) -> Result<EntityId, StoreError> {
901 let collection = Self::canonical_collection(collection);
902 let _publication = self.vault_publication_lock.lock();
904 let manager = self.get_or_create_collection(collection);
905 let mut entity = entity;
906 entity.canonicalize_system_collection_names();
907 if entity.id.raw() == 0 {
908 entity.id = self.next_entity_id();
909 } else {
910 self.register_entity_id(entity.id);
911 }
912 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
914 if *row_id == 0 {
915 *row_id = manager.next_row_id();
916 } else {
917 manager.register_row_id(*row_id);
918 }
919 }
920 entity.ensure_table_logical_id();
921
922 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
924 {
925 Some(node.label.clone())
926 } else {
927 None
928 };
929 self.resident
931 .context_index
932 .index_entity(collection, &entity);
933
934 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
937 if self.config.auto_index_refs {
938 self.index_cross_refs(&entity, collection)?;
939 }
940
941 let paged_btree = if persist_record
948 && !Self::deferred_store_wal_capture_active()
949 && self.config.resident_memory_bytes_max.is_none()
950 {
951 self.get_or_create_btree(collection)
952 } else {
953 None
954 };
955 let persist_fence = paged_btree
956 .as_ref()
957 .map(|_| self.btree_indices.read_recursive());
958 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
959 let unversioned = entity.xmin == 0 && entity.xmax == 0;
960
961 let id = manager.insert(entity)?;
962 let serialized_record = if persist_record {
963 Some(
964 manager
965 .get_with(id, |entity| {
966 Self::serialize_entity_record(entity, None, self.format_version())
967 })
968 .ok_or(StoreError::EntityNotFound(id))?,
969 )
970 } else {
971 None
972 };
973 if let Some(ref label) = graph_node_label {
981 self.update_graph_label_index(collection, label, id);
982 }
983
984 let mut registry_dirty = false;
985 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
986 let root_before = btree.root_page_id();
987
988 let key = id.raw().to_be_bytes();
989 btree.insert(&key, record).map_err(|e| {
990 StoreError::Io(std::io::Error::other(format!(
991 "B-tree insert error while inserting '{collection}'/{id}: {e}"
992 )))
993 })?;
994 registry_dirty = root_before != btree.root_page_id();
995 }
996 drop(persist_fence);
997
998 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
1002 let actions = serialized_record
1003 .map(|record| {
1004 vec![StoreWalAction::UpsertEntityRecord {
1005 collection: collection.to_string(),
1006 record,
1007 }]
1008 })
1009 .unwrap_or_default();
1010 if registry_dirty {
1011 self.mark_paged_registry_dirty();
1012 }
1013 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1014 drop(_publication);
1015 self.complete_grouped_actions(&pending, &actions)?;
1016 } else {
1017 self.finish_paged_write(actions)?;
1018 }
1019 }
1020 if unversioned && !Self::deferred_store_wal_capture_active() {
1021 if let Some(before) = before {
1022 self.note_paged_insert(collection, &manager, &before, id);
1023 }
1024 }
1025 Ok(id)
1026 }
1027
1028 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
1034 self.try_get(collection, id, usize::MAX).ok().flatten()
1037 }
1038
1039 pub fn try_get(
1040 &self,
1041 collection: &str,
1042 id: EntityId,
1043 limit_bytes: usize,
1044 ) -> Result<Option<UnifiedEntity>, StoreError> {
1045 let Some(manager) = self.get_collection(collection) else {
1046 return Ok(None);
1047 };
1048 manager.try_get(id, limit_bytes)
1049 }
1050
1051 pub fn get_table_row_by_logical_id(
1057 &self,
1058 collection: &str,
1059 logical_id: EntityId,
1060 ) -> Option<UnifiedEntity> {
1061 if let Some(entity) = self.get(collection, logical_id) {
1062 if matches!(entity.kind, EntityKind::TableRow { .. })
1063 && entity.logical_id() == logical_id
1064 && entity.xmax == 0
1065 {
1066 return Some(entity);
1067 }
1068 }
1069
1070 let manager = self.get_collection(collection)?;
1071 let mut matches = manager.query_all(|entity| {
1072 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1073 });
1074 matches
1075 .iter()
1076 .find(|entity| entity.xmax == 0)
1077 .cloned()
1078 .or_else(|| matches.pop())
1079 }
1080
1081 pub fn table_row_versions_by_logical_id(
1082 &self,
1083 collection: &str,
1084 logical_id: EntityId,
1085 ) -> Vec<UnifiedEntity> {
1086 self.resident
1087 .table_row_versions_by_logical_id(collection, logical_id)
1088 }
1089
1090 pub(crate) fn try_for_each_table_row_version(
1091 &self,
1092 collection: &str,
1093 logical_id: EntityId,
1094 mut visit: impl FnMut(&UnifiedEntity) -> bool,
1095 ) -> Result<(), StoreError> {
1096 let Some(manager) = self.get_collection(collection) else {
1097 return Ok(());
1098 };
1099 manager.try_for_each_entity(usize::MAX, |entity| {
1100 !matches!(entity.kind, EntityKind::TableRow { .. })
1101 || entity.logical_id() != logical_id
1102 || visit(entity)
1103 })
1104 }
1105
1106 pub(crate) fn try_get_table_row_by_logical_id(
1107 &self,
1108 collection: &str,
1109 logical_id: EntityId,
1110 ) -> Result<Option<UnifiedEntity>, StoreError> {
1111 let Some(manager) = self.get_collection(collection) else {
1112 return Ok(None);
1113 };
1114 if let Some(entity) = manager.try_get(logical_id, usize::MAX)? {
1115 if matches!(entity.kind, EntityKind::TableRow { .. })
1116 && entity.logical_id() == logical_id
1117 && entity.xmax == 0
1118 {
1119 return Ok(Some(entity));
1120 }
1121 }
1122 let mut selected = None;
1125 self.try_for_each_table_row_version(collection, logical_id, |entity| {
1126 selected = Some(entity.clone());
1127 entity.xmax != 0
1128 })?;
1129 manager.restore_owned_row_schemas(selected.iter_mut());
1130 Ok(selected)
1131 }
1132
1133 pub fn vacuum_mvcc_history(
1134 &self,
1135 collection: &str,
1136 cutoff_xid: u64,
1137 ) -> Result<MvccVacuumStats, StoreError> {
1138 let manager = self
1139 .get_collection(collection)
1140 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1141 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1142 manager.for_each_entity(|entity| {
1143 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1144 return true;
1145 }
1146 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1147 if entity.xmax == 0 {
1148 entry.0 = true;
1149 }
1150 entry.1 = entry.1.max(entity.xmin);
1151 true
1152 });
1153
1154 let mut stats = MvccVacuumStats::default();
1155 let mut reclaim_ids = Vec::new();
1156 manager.for_each_entity(|entity| {
1157 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1158 return true;
1159 }
1160 stats.scanned_versions += 1;
1161 let (has_live_version, max_xmin) = logical
1162 .get(&entity.logical_id())
1163 .copied()
1164 .unwrap_or((false, entity.xmin));
1165 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1166 if entity.xmax < cutoff_xid {
1167 stats.reclaimed_versions += 1;
1168 if is_delete_tombstone {
1169 stats.reclaimed_tombstones += 1;
1170 } else {
1171 stats.reclaimed_history_versions += 1;
1172 }
1173 reclaim_ids.push(entity.id);
1174 } else {
1175 stats.retained_versions += 1;
1176 if is_delete_tombstone {
1177 stats.retained_tombstones += 1;
1178 } else {
1179 stats.retained_history_versions += 1;
1180 }
1181 }
1182 true
1183 });
1184
1185 if !reclaim_ids.is_empty() {
1186 self.delete_batch(collection, &reclaim_ids)?;
1187 }
1188 Ok(stats)
1189 }
1190
1191 pub(crate) fn install_versioned_table_row_update(
1192 &self,
1193 collection: &str,
1194 old_version: UnifiedEntity,
1195 mut new_version: UnifiedEntity,
1196 metadata: Option<&Metadata>,
1197 ) -> Result<(), StoreError> {
1198 let manager = self
1199 .get_collection(collection)
1200 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1201
1202 let old_id = old_version.id;
1203 let new_id = new_version.id;
1204 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1205
1206 self.resident.entity_cache.remove(old_id.raw());
1207 self.resident.entity_cache.remove(new_id.raw());
1208 manager.update(old_version.clone())?;
1209 self.resident.context_index.remove_entity(old_id);
1210
1211 self.register_entity_id(new_version.id);
1212 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1213 if *row_id == 0 {
1214 *row_id = manager.next_row_id();
1215 } else {
1216 manager.register_row_id(*row_id);
1217 }
1218 }
1219 new_version.ensure_table_logical_id();
1220 manager.insert(new_version.clone())?;
1221 if let Some(metadata) = inherited_metadata {
1222 manager.set_metadata(new_id, metadata)?;
1223 }
1224 self.resident
1225 .context_index
1226 .index_entity(collection, &new_version);
1227 if self.config.auto_index_refs {
1228 self.index_cross_refs(&new_version, collection)?;
1229 }
1230
1231 let old_metadata = manager.get_metadata(old_id);
1232 let new_metadata = manager.get_metadata(new_id);
1233 let fv = self.format_version();
1234 let records = vec![
1237 manager
1238 .get_with(old_id, |entity| {
1239 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1240 })
1241 .ok_or(StoreError::EntityNotFound(old_id))?,
1242 manager
1243 .get_with(new_id, |entity| {
1244 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1245 })
1246 .ok_or(StoreError::EntityNotFound(new_id))?,
1247 ];
1248 self.finish_paged_write_for_projection(
1249 [StoreWalAction::BulkUpsertEntityRecords {
1250 collection: collection.to_string(),
1251 records,
1252 }],
1253 true,
1254 )?;
1255
1256 Ok(())
1257 }
1258
1259 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1265 match self.get_collection(collection) {
1266 Some(manager) => manager.get_many(ids),
1267 None => vec![None; ids.len()],
1268 }
1269 }
1270
1271 pub fn try_get_batch(
1273 &self,
1274 collection: &str,
1275 ids: &[EntityId],
1276 read_bytes_max: usize,
1277 ) -> Result<Vec<Option<UnifiedEntity>>, StoreError> {
1278 match self.get_collection(collection) {
1279 Some(manager) => manager.try_get_many(ids, read_bytes_max),
1280 None => Ok(vec![None; ids.len()]),
1281 }
1282 }
1283
1284 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1286 self.resident.get_any(id)
1287 }
1288
1289 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1295 self.resident.entity_cache_hit_rate()
1296 }
1297
1298 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1300 self.resident.entity_cache_stats()
1301 }
1302
1303 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1305 let collection = Self::canonical_collection(collection);
1306 let publication = self.vault_publication_lock.lock();
1307 self.resident.entity_cache.remove(id.raw());
1309 let manager = self
1310 .get_collection(collection)
1311 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1312
1313 let deleted = manager.delete(id)?;
1314 if !deleted {
1315 return Ok(false);
1316 }
1317
1318 let mut registry_dirty = false;
1320 if self.pager.is_some() && self.config.resident_memory_bytes_max.is_none() {
1321 let btree_indices = self.btree_indices.read();
1322 if let Some(btree) = btree_indices.get(collection) {
1323 let root_before = btree.root_page_id();
1324 let key = id.raw().to_be_bytes();
1325 let _ = btree.delete(&key);
1326 registry_dirty = root_before != btree.root_page_id();
1327 }
1328 }
1329
1330 self.unindex_cross_refs(id)?;
1332
1333 self.remove_from_graph_label_index(collection, id);
1335
1336 if registry_dirty {
1337 self.mark_paged_registry_dirty();
1338 }
1339 let actions = [StoreWalAction::DeleteEntityRecord {
1340 collection: collection.to_string(),
1341 entity_id: id.raw(),
1342 }];
1343 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1344 drop(publication);
1345 self.complete_grouped_actions(&pending, &actions)?;
1346 } else {
1347 self.finish_paged_write(actions)?;
1348 }
1349
1350 Ok(true)
1351 }
1352
1353 pub fn delete_batch(
1354 &self,
1355 collection: &str,
1356 ids: &[EntityId],
1357 ) -> Result<Vec<EntityId>, StoreError> {
1358 let collection = Self::canonical_collection(collection);
1359 if ids.is_empty() {
1360 return Ok(Vec::new());
1361 }
1362 let _publication = self.vault_publication_lock.lock();
1365
1366 self.resident
1371 .entity_cache
1372 .remove_many(ids.iter().map(|id| id.raw()));
1373
1374 let manager = self
1375 .get_collection(collection)
1376 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1377
1378 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1379 let deleted_ids = manager.delete_batch(ids)?;
1380 if deleted_ids.is_empty() {
1381 return Ok(deleted_ids);
1382 }
1383
1384 let mut registry_dirty = false;
1385 let mut updated_tree = false;
1386 if self.pager.is_some() && self.config.resident_memory_bytes_max.is_none() {
1387 let btree_indices = self.btree_indices.read();
1388 if let Some(btree) = btree_indices.get(collection) {
1389 updated_tree = true;
1390 let root_before = btree.root_page_id();
1391 for id in &deleted_ids {
1392 let key = id.raw().to_be_bytes();
1393 updated_tree &= btree.delete(&key).is_ok();
1396 }
1397 registry_dirty = root_before != btree.root_page_id();
1398 }
1399 }
1400
1401 self.unindex_cross_refs_batch(&deleted_ids)?;
1402 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1403 if registry_dirty {
1404 self.mark_paged_registry_dirty();
1405 }
1406 let actions = deleted_ids
1407 .iter()
1408 .map(|id| StoreWalAction::DeleteEntityRecord {
1409 collection: collection.to_string(),
1410 entity_id: id.raw(),
1411 })
1412 .collect::<Vec<_>>();
1413 self.finish_paged_write(actions)?;
1414
1415 if updated_tree {
1416 if let Some(before) = before {
1417 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1418 }
1419 self.flush_paged_registry()?;
1424 }
1425 Ok(deleted_ids)
1426 }
1427
1428 pub fn set_metadata(
1430 &self,
1431 collection: &str,
1432 id: EntityId,
1433 metadata: Metadata,
1434 ) -> Result<(), StoreError> {
1435 let manager = self
1436 .get_collection(collection)
1437 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1438
1439 manager.set_metadata(id, metadata)?;
1440 if let Some(entity) = manager.get(id) {
1441 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1442 }
1443 Ok(())
1444 }
1445
1446 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1448 self.get_collection(collection)?.get_metadata(id)
1449 }
1450
1451 pub fn try_get_metadata(
1453 &self,
1454 collection: &str,
1455 id: EntityId,
1456 limit_bytes: usize,
1457 ) -> Result<Option<Metadata>, StoreError> {
1458 let Some(manager) = self.get_collection(collection) else {
1459 return Ok(None);
1460 };
1461 manager.try_get_metadata(id, limit_bytes)
1462 }
1463
1464 pub fn add_cross_ref(
1466 &self,
1467 source_collection: &str,
1468 source_id: EntityId,
1469 target_collection: &str,
1470 target_id: EntityId,
1471 ref_type: RefType,
1472 weight: f32,
1473 ) -> Result<(), StoreError> {
1474 let source_collection = Self::canonical_collection(source_collection);
1475 let target_collection = Self::canonical_collection(target_collection);
1476 let registry_dirty = self.resident.add_cross_ref_in_memory(
1477 source_collection,
1478 source_id,
1479 target_collection,
1480 target_id,
1481 ref_type,
1482 weight,
1483 self.config.max_cross_refs,
1484 |entity| {
1485 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1486 },
1487 )?;
1488 if registry_dirty
1489 && matches!(
1490 self.config.durability_mode,
1491 crate::api::DurabilityMode::Strict
1492 )
1493 {
1494 self.flush_paged_state()?;
1495 }
1496 Ok(())
1497 }
1498
1499 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1501 self.resident.get_refs_from(id)
1502 }
1503
1504 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1506 self.resident.get_refs_to(id)
1507 }
1508
1509 pub fn expand_refs(
1511 &self,
1512 id: EntityId,
1513 depth: u32,
1514 ref_types: Option<&[RefType]>,
1515 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1516 let mut results = Vec::new();
1517 let mut visited = std::collections::HashSet::new();
1518 visited.insert(id);
1519
1520 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1521
1522 results
1523 }
1524
1525 fn expand_refs_recursive(
1526 &self,
1527 id: EntityId,
1528 max_depth: u32,
1529 ref_types: Option<&[RefType]>,
1530 visited: &mut std::collections::HashSet<EntityId>,
1531 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1532 current_depth: u32,
1533 ) {
1534 if current_depth > max_depth {
1535 return;
1536 }
1537
1538 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1539 if visited.contains(&target_id) {
1540 continue;
1541 }
1542
1543 if let Some(types) = ref_types {
1544 if !types.contains(&ref_type) {
1545 continue;
1546 }
1547 }
1548
1549 visited.insert(target_id);
1550
1551 if let Some(entity) = self.get(&target_collection, target_id) {
1552 results.push((entity, current_depth, ref_type));
1553
1554 self.expand_refs_recursive(
1556 target_id,
1557 max_depth,
1558 ref_types,
1559 visited,
1560 results,
1561 current_depth + 1,
1562 );
1563 }
1564 }
1565 }
1566
1567 pub(crate) fn index_cross_refs(
1569 &self,
1570 entity: &UnifiedEntity,
1571 collection: &str,
1572 ) -> Result<(), StoreError> {
1573 self.resident.index_cross_refs(entity, collection)
1574 }
1575
1576 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1578 self.resident.unindex_cross_refs(id)
1579 }
1580
1581 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1582 self.resident.unindex_cross_refs_batch(ids)
1583 }
1584
1585 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1589 self.resident.unindex_cross_refs_fast_path_hits()
1590 }
1591
1592 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1594 where
1595 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1596 {
1597 self.resident.query_all(filter)
1598 }
1599
1600 pub fn scan<F>(
1602 &self,
1603 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1604 filter: F,
1605 ) -> Vec<(String, UnifiedEntity)>
1606 where
1607 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1608 {
1609 self.resident.scan(snapshot, filter)
1610 }
1611
1612 pub fn filter_metadata_all(
1614 &self,
1615 filters: &[(String, MetadataFilter)],
1616 ) -> Vec<(String, EntityId)> {
1617 self.resident.filter_metadata_all(filters)
1618 }
1619
1620 pub fn stats(&self) -> StoreStats {
1622 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1628 let collections = self.resident.collections.read();
1629 collections
1630 .iter()
1631 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1632 .collect()
1633 };
1634
1635 let mut stats = StoreStats {
1636 collection_count: pairs.len(),
1637 ..Default::default()
1638 };
1639
1640 for (name, manager) in &pairs {
1641 let manager_stats = manager.stats();
1642 stats.total_entities += manager_stats.total_entities;
1643 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1644 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1645 stats.collections.insert(name.clone(), manager_stats);
1646 }
1647
1648 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1649 stats.total_memory_bytes += self
1650 .checkpoint_versions
1651 .read()
1652 .values()
1653 .filter_map(|version| version.deferred_rows.as_ref())
1654 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1655 .sum::<usize>();
1656 stats
1657 }
1658
1659 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1661 let collections = self.resident.collections.read();
1662 for manager in collections.values() {
1663 manager.run_maintenance()?;
1664 }
1665 Ok(())
1666 }
1667
1668 pub fn reclaimable_segment_bytes(&self) -> u64 {
1670 let collections = self.resident.collections.read();
1671 collections
1672 .values()
1673 .map(|manager| manager.reclaimable_bytes())
1674 .fold(0, u64::saturating_add)
1675 }
1676
1677 pub fn pressure_consolidation_tick(&self) -> bool {
1680 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1681 let mut advanced = false;
1684 for manager in &managers {
1685 advanced |= manager.pressure_consolidation_tick();
1686 }
1687 advanced
1688 }
1689}
1690
1691fn flatten_config_json(
1693 prefix: &str,
1694 value: &crate::serde_json::Value,
1695 out: &mut Vec<(String, reddb_types::Value)>,
1696) {
1697 use reddb_types::Value;
1698 match value {
1699 crate::serde_json::Value::Object(map) => {
1700 for (k, v) in map {
1701 let key = if prefix.is_empty() {
1702 k.clone()
1703 } else {
1704 format!("{prefix}.{k}")
1705 };
1706 flatten_config_json(&key, v, out);
1707 }
1708 }
1709 crate::serde_json::Value::String(s) => {
1710 out.push((prefix.to_string(), Value::text(s.clone())));
1711 }
1712 crate::serde_json::Value::Integer(n) => {
1713 out.push((prefix.to_string(), Value::Integer(*n)));
1714 }
1715 crate::serde_json::Value::Number(n) => {
1716 if n.fract().abs() < f64::EPSILON {
1717 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1718 } else {
1719 out.push((prefix.to_string(), Value::Float(*n)));
1720 }
1721 }
1722 crate::serde_json::Value::Decimal(n) => {
1723 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1724 }
1725 crate::serde_json::Value::Bool(b) => {
1726 out.push((prefix.to_string(), Value::Boolean(*b)));
1727 }
1728 crate::serde_json::Value::Null => {
1729 out.push((prefix.to_string(), Value::Null));
1730 }
1731 crate::serde_json::Value::Array(_) => {
1732 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1733 out.push((prefix.to_string(), Value::text(json_str)));
1734 }
1735 }
1736}