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 required_capabilities = entities.iter().fold(0, |required, entity| {
78 required | reddb_engine::storage::entity_codec::required_file_capabilities(entity)
79 });
80 self.fence_required_capabilities(required_capabilities)?;
81 let fv = self.format_version();
82 let manager = self
83 .get_collection(collection)
84 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
85 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
86 .iter()
87 .map(|entity| {
88 let metadata = manager.get_metadata(entity.id);
89 (
90 entity.id.raw().to_be_bytes().to_vec(),
91 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
92 )
93 })
94 .collect();
95 serialized.sort_by(|a, b| a.0.cmp(&b.0));
96
97 if !skip_btree
98 && !Self::deferred_store_wal_capture_active()
99 && self.config.resident_memory_bytes_max.is_none()
100 {
101 let Some(pager) = &self.pager else {
102 let records: Vec<Vec<u8>> =
103 serialized.into_iter().map(|(_id, record)| record).collect();
104 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
105 collection: collection.to_string(),
106 records,
107 }])?;
108 return Ok(());
109 };
110 let mut btree_indices = self.btree_indices.write();
111 let btree = btree_indices
112 .entry(collection.to_string())
113 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
114 let root_before = btree.root_page_id();
115 btree.upsert_batch_sorted(&serialized).map_err(|e| {
116 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
117 })?;
118 let root_after = btree.root_page_id();
119 drop(btree_indices);
120 if root_before != root_after {
121 self.mark_paged_registry_dirty();
122 }
123 }
124 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
125 self.finish_paged_write_for_projection(
126 [StoreWalAction::BulkUpsertEntityRecords {
127 collection: collection.to_string(),
128 records,
129 }],
130 skip_btree,
131 )?;
132 Ok(())
133 }
134
135 pub(crate) fn persist_entities_to_pager_wal_only(
147 &self,
148 collection: &str,
149 entities: &[UnifiedEntity],
150 ) -> Result<(), StoreError> {
151 self.persist_entities_impl(collection, entities, true)
152 }
153
154 fn persist_entities_impl(
155 &self,
156 collection: &str,
157 entities: &[UnifiedEntity],
158 skip_btree: bool,
159 ) -> Result<(), StoreError> {
160 let collection = Self::canonical_collection(collection);
161 let skip_btree = skip_btree || self.config.resident_memory_bytes_max.is_some();
162 if entities.is_empty() {
163 return Ok(());
164 }
165
166 if self.is_read_only() || (self.pager.is_none() && self.config.embedded_wal_path.is_none())
167 {
168 return Ok(());
169 }
170
171 let required_capabilities = entities.iter().fold(0, |required, entity| {
172 required | reddb_engine::storage::entity_codec::required_file_capabilities(entity)
173 });
174 self.fence_required_capabilities(required_capabilities)?;
175
176 let fv = self.format_version();
177 let manager = self
178 .get_collection(collection)
179 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
180 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
181 .iter()
182 .map(|entity| {
183 let metadata = manager.get_metadata(entity.id);
184 (
185 entity.id.raw().to_be_bytes().to_vec(),
186 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
187 )
188 })
189 .collect();
190 serialized.sort_by(|a, b| a.0.cmp(&b.0));
192
193 if !skip_btree
194 && !Self::deferred_store_wal_capture_active()
195 && self.config.resident_memory_bytes_max.is_none()
196 {
197 let Some(pager) = &self.pager else {
198 let records: Vec<Vec<u8>> =
199 serialized.into_iter().map(|(_id, record)| record).collect();
200 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
201 collection: collection.to_string(),
202 records,
203 }])?;
204 return Ok(());
205 };
206 let mut btree_indices = self.btree_indices.write();
207 let btree = btree_indices
208 .entry(collection.to_string())
209 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
210 let root_before = btree.root_page_id();
211
212 btree.upsert_batch_sorted(&serialized).map_err(|e| {
216 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
217 })?;
218 let root_after = btree.root_page_id();
219 drop(btree_indices);
220 if root_before != root_after {
221 self.mark_paged_registry_dirty();
222 }
223 }
224 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
234 self.finish_paged_write_for_projection(
235 [StoreWalAction::BulkUpsertEntityRecords {
236 collection: collection.to_string(),
237 records,
238 }],
239 skip_btree,
240 )?;
241
242 Ok(())
243 }
244
245 pub(crate) fn update_graph_label_index(
247 &self,
248 collection: &str,
249 label: &str,
250 entity_id: EntityId,
251 ) {
252 self.resident
253 .update_graph_label_index(collection, label, entity_id)
254 }
255
256 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
258 self.resident
259 .remove_from_graph_label_index(collection, entity_id)
260 }
261
262 pub(crate) fn remove_from_graph_label_index_batch(
263 &self,
264 collection: &str,
265 entity_ids: &[EntityId],
266 ) {
267 self.resident
268 .remove_from_graph_label_index_batch(collection, entity_ids)
269 }
270
271 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
273 self.resident.lookup_graph_nodes_by_label(label)
274 }
275
276 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
278 self.resident
279 .lookup_graph_nodes_by_label_in(collection, label)
280 }
281
282 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
283 let name = reddb_engine::storage::system_collection_names::canonical_owned(name.into());
284 let _publication = self.vault_publication_lock.lock();
286 let mut collections = self.resident.collections.write();
287
288 if collections.contains_key(&name) {
289 return Err(StoreError::CollectionExists(name));
290 }
291
292 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
293 collections.insert(name.clone(), Arc::new(manager));
294 drop(collections);
295
296 self.mark_paged_registry_dirty();
297 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
298
299 Ok(())
300 }
301
302 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
304 self.resident.get_or_create_collection(
305 reddb_engine::storage::system_collection_names::canonical_owned(name.into()),
306 &self.config.manager_config,
307 )
308 }
309
310 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
312 self.resident
313 .get_collection(Self::canonical_collection(name))
314 }
315
316 pub fn context_index(&self) -> &ContextIndex {
318 self.resident.context_index()
319 }
320
321 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
327 let mut map = self.replayed_turbo_inserts.lock();
328 map.remove(Self::canonical_collection(collection))
329 }
330
331 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
332 let mut deltas = self.replayed_probabilistic_deltas.lock();
333 std::mem::take(&mut *deltas)
334 }
335
336 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
343 self.try_set_config_tree(prefix, json).unwrap_or_default()
346 }
347
348 pub fn try_set_config_tree(
349 &self,
350 prefix: &str,
351 json: &crate::serde_json::Value,
352 ) -> Result<usize, StoreError> {
353 super::config_keys::validate_config_key(prefix)?;
354 self.try_set_key_value_tree("red.config", prefix, json)
355 }
356
357 pub fn try_set_storage_metadata(
359 &self,
360 key: &str,
361 value: &crate::serde_json::Value,
362 ) -> Result<usize, StoreError> {
363 self.try_set_key_value_tree("red.storage.metadata", key, value)
364 }
365
366 pub(crate) fn try_set_fork_metadata(
367 &self,
368 key: &str,
369 value: &crate::serde_json::Value,
370 ) -> Result<usize, StoreError> {
371 self.try_set_key_value_tree("red.storage.forks", key, value)
372 }
373
374 pub(crate) fn try_get_fork_metadata(
375 &self,
376 key: &str,
377 ) -> Result<Option<reddb_types::Value>, StoreError> {
378 self.try_get_key_value("red.storage.forks", key)
379 }
380
381 fn try_set_key_value_tree(
382 &self,
383 collection: &str,
384 prefix: &str,
385 json: &crate::serde_json::Value,
386 ) -> Result<usize, StoreError> {
387 let _publication = self.vault_publication_lock.lock();
388 let mut pairs = Vec::new();
389 flatten_config_json(prefix, json, &mut pairs);
390 if Self::canonical_collection(collection) == "red.config" {
391 for (key, _) in &pairs {
392 super::config_keys::validate_config_key(key)?;
393 }
394 }
395 let manager = self.get_or_create_collection(collection);
396 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
398 .iter()
399 .map(|(key, _)| (key.clone(), Vec::new()))
400 .collect();
401 let mut capture = |entity: &UnifiedEntity| {
402 if let EntityData::Row(row) = &entity.data {
403 if let Some(reddb_types::Value::Text(key)) = row.get_field("key") {
404 if let Some(rows) = previous.get_mut(key.as_ref()) {
405 rows.push((entity.id, row.get_field("value").cloned()));
406 }
407 }
408 }
409 true
410 };
411 if manager.has_backing() {
412 for (key, _) in &pairs {
413 manager.try_for_each_key_entity(key, &mut capture)?;
414 }
415 } else {
416 manager.try_for_each_entity(usize::MAX, &mut capture)?;
417 }
418 let batch_system = collection == "red.config"
419 && prefix == "system"
420 && self.config.embedded_wal_path.is_some()
421 && !Self::deferred_store_wal_capture_active();
422 let mut system_rows = Vec::new();
423 let mut saved = 0;
424 for (key, value) in pairs {
425 let stale = previous.remove(&key).unwrap_or_default();
426 if let [(_, Some(current))] = stale.as_slice() {
430 if *current == value {
431 saved += 1;
432 continue;
433 }
434 }
435 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
436 let entity = UnifiedEntity::new(
437 EntityId::new(0),
438 EntityKind::TableRow {
439 table: Arc::from(collection),
440 row_id: 0,
441 },
442 EntityData::Row(RowData {
443 columns: Vec::new(),
444 named: Some(
445 [
446 ("key".to_string(), reddb_types::Value::text(key)),
447 ("value".to_string(), value),
448 ]
449 .into_iter()
450 .collect(),
451 ),
452 schema: None,
453 }),
454 );
455 if batch_system {
456 system_rows.push((entity, stale));
457 saved += 1;
458 continue;
459 }
460 self.insert_auto(collection, entity)?;
463 saved += 1;
464 if !stale.is_empty() {
465 self.delete_batch(collection, &stale)?;
466 }
467 }
468 if !system_rows.is_empty() {
469 self.write_system_config_batch(system_rows)?;
470 }
471 Ok(saved)
472 }
473
474 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
478 self.try_get_config(key).ok().flatten()
481 }
482
483 pub fn try_get_config(&self, key: &str) -> Result<Option<reddb_types::Value>, StoreError> {
484 super::config_keys::validate_config_key(key)?;
485 self.try_get_key_value("red.config", key)
486 }
487
488 pub(crate) fn try_get_config_entity(
489 &self,
490 key: &str,
491 ) -> Result<Option<UnifiedEntity>, StoreError> {
492 super::config_keys::validate_config_key(key)?;
493 self.try_get_key_value_entity("red.config", key)
494 }
495
496 pub fn try_get_storage_metadata(
497 &self,
498 key: &str,
499 ) -> Result<Option<reddb_types::Value>, StoreError> {
500 self.try_get_key_value("red.storage.metadata", key)
501 }
502
503 fn try_get_key_value(
504 &self,
505 collection: &str,
506 key: &str,
507 ) -> Result<Option<reddb_types::Value>, StoreError> {
508 Ok(self
509 .try_get_key_value_entity(collection, key)?
510 .and_then(|entity| {
511 entity
512 .data
513 .as_row()
514 .and_then(|row| row.get_field("value"))
515 .cloned()
516 }))
517 }
518
519 fn try_get_key_value_entity(
520 &self,
521 collection: &str,
522 key: &str,
523 ) -> Result<Option<UnifiedEntity>, StoreError> {
524 let Some(manager) = self.get_collection(collection) else {
525 return Ok(None);
526 };
527 let _publication = manager
528 .has_backing()
529 .then(|| self.vault_publication_lock.lock());
530 let mut latest: Option<UnifiedEntity> = None;
531 manager.try_for_each_key_entity(key, |entity| {
532 if let EntityData::Row(row) = &entity.data {
533 let key_matches = matches!(row.get_field("key"), Some(reddb_types::Value::Text(stored)) if stored.as_ref() == key);
534 if key_matches
535 && row.get_field("value").is_some()
536 && latest.as_ref().is_none_or(|current| entity.id >= current.id)
537 {
538 latest = Some(entity.clone());
539 }
540 }
541 true
542 })?;
543 Ok(latest)
544 }
545
546 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
551 self.resident.set_aux_metadata(bytes)
552 }
553
554 pub(crate) fn persist_aux_metadata(&self, bytes: Vec<u8>) -> Result<(), StoreError> {
557 let bytes = reddb_engine::catalog::codec::canonicalize_collection_contract_blob(&bytes)
558 .map_err(|err| StoreError::Serialization(err.to_string()))?;
559 let _publication = self.vault_publication_lock.lock();
560 self.finish_paged_write([StoreWalAction::SetAuxMetadata {
561 bytes: bytes.clone(),
562 }])?;
563 self.resident.set_aux_metadata(bytes);
564 Ok(())
565 }
566
567 pub fn aux_metadata(&self) -> Vec<u8> {
569 self.resident.aux_metadata()
570 }
571
572 pub fn list_collections(&self) -> Vec<String> {
574 self.resident.list_collections()
575 }
576
577 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
579 let name = Self::canonical_collection(name);
580 let _vault_guard = self.vault_publication_lock.lock();
581 let manager = {
582 let mut collections = self.resident.collections.write();
583
584 collections
585 .remove(name)
586 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
587 };
588
589 let entities = manager.query_all(|_| true);
590 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
591
592 for entity_id in &entity_ids {
593 self.resident.context_index.remove_entity(*entity_id);
594 let _ = self.unindex_cross_refs(*entity_id);
595 }
596
597 if let Some(tree) = self.btree_indices.write().remove(name) {
598 self.retire_btree(tree);
599 }
600 if let Some(index) = self.key_indices.write().remove(name) {
601 for tree in index.trees() {
602 self.retire_btree(Arc::clone(tree));
603 }
604 }
605 self.secondary_index_specs.write().remove(name);
606
607 self.resident
608 .entity_cache
609 .retain(|entity_id, (collection, _)| {
610 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
611 });
612
613 self.resident.cross_refs.write().retain(|source_id, refs| {
614 refs.retain(|(target_id, _, target_collection)| {
615 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
616 });
617 !entity_ids.iter().any(|id| id == source_id)
618 });
619
620 self.resident
621 .reverse_refs
622 .write()
623 .retain(|target_id, refs| {
624 refs.retain(|(source_id, _, source_collection)| {
625 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
626 });
627 !entity_ids.iter().any(|id| id == target_id)
628 });
629
630 self.mark_paged_registry_dirty();
631 self.finish_paged_write([StoreWalAction::DropCollection {
632 name: name.to_string(),
633 }])?;
634
635 Ok(())
636 }
637
638 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
640 let collection = Self::canonical_collection(collection);
641 super::config_keys::validate_config_entity(collection, &entity)?;
642 let manager = self
643 .get_collection(collection)
644 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
645
646 let mut entity = entity;
647 entity.canonicalize_system_collection_names();
648 if entity.id.raw() == 0 {
649 entity.id = self.next_entity_id();
650 } else {
651 self.register_entity_id(entity.id);
652 }
653 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
655 if *row_id == 0 {
656 *row_id = manager.next_row_id();
657 } else {
658 manager.register_row_id(*row_id);
659 }
660 }
661 entity.ensure_table_logical_id();
662 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
664 {
665 Some(node.label.clone())
666 } else {
667 None
668 };
669
670 let id = manager.insert(entity)?;
671 self.register_entity_id(id);
672
673 if let Some(ref label) = graph_node_label {
675 self.update_graph_label_index(collection, label, id);
676 }
677
678 let mut registry_dirty = false;
681 if let Some(pager) = self.pager.as_ref().filter(|_| {
682 !Self::deferred_store_wal_capture_active()
683 && self.config.resident_memory_bytes_max.is_none()
684 }) {
685 if let Some(entity) = manager.get(id) {
686 let mut btree_indices = self.btree_indices.write();
687 let btree = btree_indices
688 .entry(collection.to_string())
689 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
690 let root_before = btree.root_page_id();
691
692 let key = id.raw().to_be_bytes();
693 let metadata = manager.get_metadata(id);
694 let value = Self::serialize_entity_record(
695 &entity,
696 metadata.as_ref(),
697 self.format_version(),
698 );
699 let _ = btree.insert(&key, &value);
701 registry_dirty = root_before != btree.root_page_id();
702 }
703 }
704
705 if self.config.auto_index_refs {
707 if let Some(entity) = manager.get(id) {
708 self.index_cross_refs(&entity, collection)?;
709 }
710 }
711
712 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
716 let actions = manager
717 .get(id)
718 .map(|entity| {
719 let metadata = manager.get_metadata(id);
720 vec![StoreWalAction::upsert_entity(
721 collection,
722 &entity,
723 metadata.as_ref(),
724 self.format_version(),
725 )]
726 })
727 .unwrap_or_default();
728 if registry_dirty {
729 self.mark_paged_registry_dirty();
730 }
731 self.finish_paged_write(actions)?;
732 } else {
733 self.note_in_memory_result_mutation(collection)?;
734 }
735
736 Ok(id)
737 }
738
739 pub fn bulk_insert(
744 &self,
745 collection: &str,
746 mut entities: Vec<UnifiedEntity>,
747 ) -> Result<Vec<EntityId>, StoreError> {
748 let collection = Self::canonical_collection(collection);
749 for entity in &mut entities {
750 entity.canonicalize_system_collection_names();
751 super::config_keys::validate_config_entity(collection, entity)?;
752 }
753 let _publication = self.vault_publication_lock.lock();
756 let trace = matches!(
760 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
761 Some("1") | Some("true") | Some("on")
762 );
763 let t_start = std::time::Instant::now();
764 let n = entities.len();
765 let manager = self.get_or_create_collection(collection);
766 let t_get_coll = t_start.elapsed();
767
768 let t0 = std::time::Instant::now();
777 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
778 let n_missing_row_ids = entities
779 .iter()
780 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
781 .count() as u64;
782 let mut entity_id_range = if n_missing_entity_ids > 0 {
783 self.reserve_entity_ids(n_missing_entity_ids)
784 } else {
785 0..0
786 };
787 let mut row_id_range = if n_missing_row_ids > 0 {
788 manager.reserve_row_ids(n_missing_row_ids)
789 } else {
790 0..0
791 };
792 for entity in &mut entities {
793 if entity.id.raw() == 0 {
794 let next = entity_id_range
795 .next()
796 .expect("reserved entity-id range exhausted");
797 entity.id = EntityId::new(next);
798 } else {
799 self.register_entity_id(entity.id);
800 }
801 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
802 if *row_id == 0 {
803 *row_id = row_id_range
804 .next()
805 .expect("reserved row-id range exhausted");
806 } else {
807 manager.register_row_id(*row_id);
808 }
809 }
810 entity.ensure_table_logical_id();
811 }
812 let t_assign_ids = t0.elapsed();
813
814 let graph_labels: Vec<Option<(String, EntityId)>> = entities
816 .iter()
817 .map(|e| {
818 if let EntityKind::GraphNode(ref node) = e.kind {
819 Some((node.label.clone(), e.id))
820 } else {
821 None
822 }
823 })
824 .collect();
825
826 let t0 = std::time::Instant::now();
829 let ids = manager.bulk_insert(entities)?;
830 let t_manager = t0.elapsed();
831 for id in &ids {
832 self.register_entity_id(*id);
833 }
834
835 let t0 = std::time::Instant::now();
842 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
843 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
844 let fv = self.format_version();
845 let serial_map = |id: &EntityId| {
846 manager
847 .get_with(*id, |entity| {
848 (
849 id.raw().to_be_bytes().to_vec(),
850 Self::serialize_entity_record(entity, None, fv),
851 )
852 })
853 .expect("bulk-inserted entity must remain in the fenced segment")
854 };
855 if ids.len() >= 512 {
861 use rayon::prelude::*;
862 Some(ids.par_iter().map(serial_map).collect())
863 } else {
864 Some(ids.iter().map(serial_map).collect())
865 }
866 } else {
867 None
868 };
869 let t_serialize = t0.elapsed();
870
871 for (label, entity_id) in graph_labels.iter().flatten() {
873 self.update_graph_label_index(collection, label, *entity_id);
874 }
875
876 let skip_btree_requested = matches!(
886 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
887 .ok()
888 .as_deref(),
889 Some("1") | Some("true") | Some("on")
890 );
891 let skip_btree = skip_btree_requested && self.pager.is_none();
894 if skip_btree_requested && !skip_btree {
895 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
897 IGNORED.get_or_init(|| {
898 tracing::warn!(
899 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
900 active — flag ignored; bulk inserts will be persisted normally"
901 );
902 });
903 } else if skip_btree {
904 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
906 WARNED.get_or_init(|| {
907 tracing::warn!(
908 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
909 bulk inserts NOT durable; data will be lost on restart"
910 );
911 });
912 }
913
914 let mut t_btree_lock = std::time::Duration::ZERO;
918 let mut t_btree_insert = std::time::Duration::ZERO;
919 let mut t_flush = std::time::Duration::ZERO;
920 if !skip_btree
921 && !Self::deferred_store_wal_capture_active()
922 && self.config.resident_memory_bytes_max.is_none()
923 {
924 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
925 let t0 = std::time::Instant::now();
926 let mut btree_indices = self.btree_indices.write();
927 let btree = btree_indices
928 .entry(collection.to_string())
929 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
930 let root_before = btree.root_page_id();
931 t_btree_lock = t0.elapsed();
932
933 let t0 = std::time::Instant::now();
934 let _ = btree.bulk_insert_sorted(batch);
935 t_btree_insert = t0.elapsed();
936 let registry_dirty = root_before != btree.root_page_id();
937
938 let t0 = std::time::Instant::now();
939 if registry_dirty {
940 self.mark_paged_registry_dirty();
941 }
942 t_flush = t0.elapsed();
943 }
944 }
945
946 let actions = serialized
952 .map(|batch| {
953 let records: Vec<Vec<u8>> =
954 batch.into_iter().map(|(_key, record)| record).collect();
955 vec![StoreWalAction::BulkUpsertEntityRecords {
956 collection: collection.to_string(),
957 records,
958 }]
959 })
960 .unwrap_or_default();
961 if let Err(error) = self.finish_paged_write(actions) {
962 for id in &ids {
965 self.apply_replayed_action(
966 &StoreWalAction::DeleteEntityRecord {
967 collection: collection.to_string(),
968 entity_id: id.raw(),
969 },
970 true,
971 )?;
972 }
973 return Err(error);
974 }
975
976 if trace {
977 tracing::debug!(
978 n,
979 total = ?t_start.elapsed(),
980 get_coll = ?t_get_coll,
981 assign = ?t_assign_ids,
982 serialize = ?t_serialize,
983 manager = ?t_manager,
984 btree_lock = ?t_btree_lock,
985 btree = ?t_btree_insert,
986 flush = ?t_flush,
987 "bulk_insert timing"
988 );
989 }
990
991 Ok(ids)
992 }
993
994 pub fn insert_auto(
996 &self,
997 collection: &str,
998 entity: UnifiedEntity,
999 ) -> Result<EntityId, StoreError> {
1000 let collection = Self::canonical_collection(collection);
1001 super::config_keys::validate_config_entity(collection, &entity)?;
1002 let mut publication = Some(self.vault_publication_lock.lock());
1004 let manager = self.get_or_create_collection(collection);
1005 let mut entity = entity;
1006 entity.canonicalize_system_collection_names();
1007 if entity.id.raw() == 0 {
1008 entity.id = self.next_entity_id();
1009 } else {
1010 self.register_entity_id(entity.id);
1011 }
1012 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
1014 if *row_id == 0 {
1015 *row_id = manager.next_row_id();
1016 } else {
1017 manager.register_row_id(*row_id);
1018 }
1019 }
1020 entity.ensure_table_logical_id();
1021
1022 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
1024 {
1025 Some(node.label.clone())
1026 } else {
1027 None
1028 };
1029 self.resident
1031 .context_index
1032 .index_entity(collection, &entity);
1033
1034 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
1037 if self.config.auto_index_refs {
1038 self.index_cross_refs(&entity, collection)?;
1039 }
1040
1041 let paged_btree = if persist_record
1048 && !Self::deferred_store_wal_capture_active()
1049 && self.config.resident_memory_bytes_max.is_none()
1050 {
1051 self.get_or_create_btree(collection)
1052 } else {
1053 None
1054 };
1055 let persist_fence = paged_btree
1056 .as_ref()
1057 .map(|_| self.btree_indices.read_recursive());
1058 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
1059 let unversioned = entity.xmin == 0 && entity.xmax == 0;
1060
1061 let id = manager.insert(entity)?;
1062 let serialized_record = if persist_record {
1063 Some(
1064 manager
1065 .get_with(id, |entity| {
1066 Self::serialize_entity_record(entity, None, self.format_version())
1067 })
1068 .ok_or(StoreError::EntityNotFound(id))?,
1069 )
1070 } else {
1071 None
1072 };
1073 if let Some(ref label) = graph_node_label {
1081 self.update_graph_label_index(collection, label, id);
1082 }
1083
1084 let mut registry_dirty = false;
1085 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
1086 let root_before = btree.root_page_id();
1087
1088 let key = id.raw().to_be_bytes();
1089 btree.insert(&key, record).map_err(|e| {
1090 StoreError::Io(std::io::Error::other(format!(
1091 "B-tree insert error while inserting '{collection}'/{id}: {e}"
1092 )))
1093 })?;
1094 registry_dirty = root_before != btree.root_page_id();
1095 }
1096 drop(persist_fence);
1097
1098 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
1102 let actions = serialized_record
1103 .map(|record| {
1104 vec![StoreWalAction::UpsertEntityRecord {
1105 collection: collection.to_string(),
1106 record,
1107 }]
1108 })
1109 .unwrap_or_default();
1110 if registry_dirty {
1111 self.mark_paged_registry_dirty();
1112 }
1113 let mut pending = None;
1116 let persisted = (|| {
1117 pending = self.enqueue_grouped_actions(&actions)?;
1118 if let Some(pending) = pending.as_mut() {
1119 drop(publication.take());
1120 self.complete_grouped_actions(pending, &actions)
1121 } else {
1122 self.finish_paged_write(actions)
1123 }
1124 })();
1125 if let Err(error) = persisted {
1126 let _publication = self.vault_publication_lock.lock();
1127 self.apply_replayed_action(
1128 &StoreWalAction::DeleteEntityRecord {
1129 collection: collection.to_string(),
1130 entity_id: id.raw(),
1131 },
1132 true,
1133 )?;
1134 return Err(error);
1135 }
1136 } else {
1137 self.note_in_memory_result_mutation(collection)?;
1138 }
1139 if unversioned && !Self::deferred_store_wal_capture_active() {
1140 if let Some(before) = before {
1141 self.note_paged_insert(collection, &manager, &before, id);
1142 }
1143 }
1144 Ok(id)
1145 }
1146
1147 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
1153 self.try_get(collection, id, usize::MAX).ok().flatten()
1156 }
1157
1158 pub fn try_get(
1159 &self,
1160 collection: &str,
1161 id: EntityId,
1162 limit_bytes: usize,
1163 ) -> Result<Option<UnifiedEntity>, StoreError> {
1164 let Some(manager) = self.get_collection(collection) else {
1165 return Ok(None);
1166 };
1167 manager.try_get(id, limit_bytes)
1168 }
1169
1170 pub fn get_table_row_by_logical_id(
1176 &self,
1177 collection: &str,
1178 logical_id: EntityId,
1179 ) -> Option<UnifiedEntity> {
1180 if let Some(entity) = self.get(collection, logical_id) {
1181 if matches!(entity.kind, EntityKind::TableRow { .. })
1182 && entity.logical_id() == logical_id
1183 && entity.xmax == 0
1184 {
1185 return Some(entity);
1186 }
1187 }
1188
1189 let manager = self.get_collection(collection)?;
1190 let mut matches = manager.query_all(|entity| {
1191 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1192 });
1193 matches
1194 .iter()
1195 .find(|entity| entity.xmax == 0)
1196 .cloned()
1197 .or_else(|| matches.pop())
1198 }
1199
1200 pub fn table_row_versions_by_logical_id(
1201 &self,
1202 collection: &str,
1203 logical_id: EntityId,
1204 ) -> Vec<UnifiedEntity> {
1205 self.resident
1206 .table_row_versions_by_logical_id(collection, logical_id)
1207 }
1208
1209 pub(crate) fn try_for_each_table_row_version(
1210 &self,
1211 collection: &str,
1212 logical_id: EntityId,
1213 mut visit: impl FnMut(&UnifiedEntity) -> bool,
1214 ) -> Result<(), StoreError> {
1215 let Some(manager) = self.get_collection(collection) else {
1216 return Ok(());
1217 };
1218 manager.try_for_each_entity(usize::MAX, |entity| {
1219 !matches!(entity.kind, EntityKind::TableRow { .. })
1220 || entity.logical_id() != logical_id
1221 || visit(entity)
1222 })
1223 }
1224
1225 pub(crate) fn try_get_table_row_by_logical_id(
1226 &self,
1227 collection: &str,
1228 logical_id: EntityId,
1229 ) -> Result<Option<UnifiedEntity>, StoreError> {
1230 let Some(manager) = self.get_collection(collection) else {
1231 return Ok(None);
1232 };
1233 if let Some(entity) = manager.try_get(logical_id, usize::MAX)? {
1234 if matches!(entity.kind, EntityKind::TableRow { .. })
1235 && entity.logical_id() == logical_id
1236 && entity.xmax == 0
1237 {
1238 return Ok(Some(entity));
1239 }
1240 }
1241 let mut selected = None;
1244 self.try_for_each_table_row_version(collection, logical_id, |entity| {
1245 selected = Some(entity.clone());
1246 entity.xmax != 0
1247 })?;
1248 manager.restore_owned_row_schemas(selected.iter_mut());
1249 Ok(selected)
1250 }
1251
1252 pub fn vacuum_mvcc_history(
1253 &self,
1254 collection: &str,
1255 cutoff_xid: u64,
1256 ) -> Result<MvccVacuumStats, StoreError> {
1257 let manager = self
1258 .get_collection(collection)
1259 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1260 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1261 manager.for_each_entity(|entity| {
1262 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1263 return true;
1264 }
1265 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1266 if entity.xmax == 0 {
1267 entry.0 = true;
1268 }
1269 entry.1 = entry.1.max(entity.xmin);
1270 true
1271 });
1272
1273 let mut stats = MvccVacuumStats::default();
1274 let mut reclaim_ids = Vec::new();
1275 manager.for_each_entity(|entity| {
1276 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1277 return true;
1278 }
1279 stats.scanned_versions += 1;
1280 let (has_live_version, max_xmin) = logical
1281 .get(&entity.logical_id())
1282 .copied()
1283 .unwrap_or((false, entity.xmin));
1284 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1285 if entity.xmax < cutoff_xid {
1286 stats.reclaimed_versions += 1;
1287 if is_delete_tombstone {
1288 stats.reclaimed_tombstones += 1;
1289 } else {
1290 stats.reclaimed_history_versions += 1;
1291 }
1292 reclaim_ids.push(entity.id);
1293 } else {
1294 stats.retained_versions += 1;
1295 if is_delete_tombstone {
1296 stats.retained_tombstones += 1;
1297 } else {
1298 stats.retained_history_versions += 1;
1299 }
1300 }
1301 true
1302 });
1303
1304 if !reclaim_ids.is_empty() {
1305 self.delete_batch(collection, &reclaim_ids)?;
1306 }
1307 Ok(stats)
1308 }
1309
1310 pub(crate) fn install_versioned_table_row_update(
1311 &self,
1312 collection: &str,
1313 old_version: UnifiedEntity,
1314 mut new_version: UnifiedEntity,
1315 metadata: Option<&Metadata>,
1316 ) -> Result<(), StoreError> {
1317 super::config_keys::validate_config_entity(collection, &new_version)?;
1318 let manager = self
1319 .get_collection(collection)
1320 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1321
1322 let old_id = old_version.id;
1323 let new_id = new_version.id;
1324 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1325
1326 self.resident.entity_cache.remove(old_id.raw());
1327 self.resident.entity_cache.remove(new_id.raw());
1328 manager.update(old_version.clone())?;
1329 #[cfg(test)]
1330 if let Some(hook) = self.test_versioned_update_after_old.lock().clone() {
1331 hook();
1332 }
1333 #[cfg(test)]
1334 if self
1335 .test_fail_versioned_update_phase
1336 .compare_exchange(1, 0, Ordering::SeqCst, Ordering::SeqCst)
1337 .is_ok()
1338 {
1339 return Err(StoreError::Io(std::io::Error::other(
1340 "injected update failure after old version",
1341 )));
1342 }
1343 self.resident.context_index.remove_entity(old_id);
1344
1345 self.register_entity_id(new_version.id);
1346 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1347 if *row_id == 0 {
1348 *row_id = manager.next_row_id();
1349 } else {
1350 manager.register_row_id(*row_id);
1351 }
1352 }
1353 new_version.ensure_table_logical_id();
1354 manager.insert(new_version.clone())?;
1355 #[cfg(test)]
1356 if self
1357 .test_fail_versioned_update_phase
1358 .compare_exchange(2, 0, Ordering::SeqCst, Ordering::SeqCst)
1359 .is_ok()
1360 {
1361 return Err(StoreError::Io(std::io::Error::other(
1362 "injected update failure after new version",
1363 )));
1364 }
1365 if let Some(metadata) = inherited_metadata {
1366 manager.set_metadata(new_id, metadata)?;
1367 }
1368 self.resident
1369 .context_index
1370 .index_entity(collection, &new_version);
1371 if self.config.auto_index_refs {
1372 self.index_cross_refs(&new_version, collection)?;
1373 }
1374
1375 let old_metadata = manager.get_metadata(old_id);
1376 let new_metadata = manager.get_metadata(new_id);
1377 let fv = self.format_version();
1378 let records = vec![
1381 manager
1382 .get_with(old_id, |entity| {
1383 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1384 })
1385 .ok_or(StoreError::EntityNotFound(old_id))?,
1386 manager
1387 .get_with(new_id, |entity| {
1388 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1389 })
1390 .ok_or(StoreError::EntityNotFound(new_id))?,
1391 ];
1392 self.finish_paged_write_for_projection(
1393 [StoreWalAction::BulkUpsertEntityRecords {
1394 collection: collection.to_string(),
1395 records,
1396 }],
1397 true,
1398 )?;
1399
1400 Ok(())
1401 }
1402
1403 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1409 match self.get_collection(collection) {
1410 Some(manager) => manager.get_many(ids),
1411 None => vec![None; ids.len()],
1412 }
1413 }
1414
1415 pub fn try_get_batch(
1417 &self,
1418 collection: &str,
1419 ids: &[EntityId],
1420 read_bytes_max: usize,
1421 ) -> Result<Vec<Option<UnifiedEntity>>, StoreError> {
1422 match self.get_collection(collection) {
1423 Some(manager) => manager.try_get_many(ids, read_bytes_max),
1424 None => Ok(vec![None; ids.len()]),
1425 }
1426 }
1427
1428 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1430 self.resident.get_any(id)
1431 }
1432
1433 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1439 self.resident.entity_cache_hit_rate()
1440 }
1441
1442 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1444 self.resident.entity_cache_stats()
1445 }
1446
1447 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1449 let collection = Self::canonical_collection(collection);
1450 let publication = self.vault_publication_lock.lock();
1451 self.resident.entity_cache.remove(id.raw());
1453 let manager = self
1454 .get_collection(collection)
1455 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1456
1457 let deleted = manager.delete(id)?;
1458 if !deleted {
1459 return Ok(false);
1460 }
1461
1462 let mut registry_dirty = false;
1464 if self.pager.is_some()
1465 && !self.is_read_only()
1466 && self.config.resident_memory_bytes_max.is_none()
1467 {
1468 let btree_indices = self.btree_indices.read();
1469 if let Some(btree) = btree_indices.get(collection) {
1470 let root_before = btree.root_page_id();
1471 let key = id.raw().to_be_bytes();
1472 let _ = btree.delete(&key);
1473 registry_dirty = root_before != btree.root_page_id();
1474 }
1475 }
1476
1477 self.unindex_cross_refs(id)?;
1479
1480 self.remove_from_graph_label_index(collection, id);
1482
1483 if registry_dirty {
1484 self.mark_paged_registry_dirty();
1485 }
1486 let actions = [StoreWalAction::DeleteEntityRecord {
1487 collection: collection.to_string(),
1488 entity_id: id.raw(),
1489 }];
1490 if let Some(mut pending) = self.enqueue_grouped_actions(&actions)? {
1491 drop(publication);
1492 self.complete_grouped_actions(&mut pending, &actions)?;
1493 } else {
1494 self.finish_paged_write(actions)?;
1495 }
1496
1497 Ok(true)
1498 }
1499
1500 pub fn delete_batch(
1501 &self,
1502 collection: &str,
1503 ids: &[EntityId],
1504 ) -> Result<Vec<EntityId>, StoreError> {
1505 let collection = Self::canonical_collection(collection);
1506 if ids.is_empty() {
1507 return Ok(Vec::new());
1508 }
1509 let _publication = self.vault_publication_lock.lock();
1512
1513 self.resident
1518 .entity_cache
1519 .remove_many(ids.iter().map(|id| id.raw()));
1520
1521 let manager = self
1522 .get_collection(collection)
1523 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1524
1525 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1526 let deleted_ids = manager.delete_batch(ids)?;
1527 if deleted_ids.is_empty() {
1528 return Ok(deleted_ids);
1529 }
1530
1531 let mut registry_dirty = false;
1532 let mut updated_tree = false;
1533 if self.pager.is_some()
1534 && !self.is_read_only()
1535 && self.config.resident_memory_bytes_max.is_none()
1536 {
1537 let btree_indices = self.btree_indices.read();
1538 if let Some(btree) = btree_indices.get(collection) {
1539 updated_tree = true;
1540 let root_before = btree.root_page_id();
1541 for id in &deleted_ids {
1542 let key = id.raw().to_be_bytes();
1543 updated_tree &= btree.delete(&key).is_ok();
1546 }
1547 registry_dirty = root_before != btree.root_page_id();
1548 }
1549 }
1550
1551 self.unindex_cross_refs_batch(&deleted_ids)?;
1552 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1553 if registry_dirty {
1554 self.mark_paged_registry_dirty();
1555 }
1556 let actions = deleted_ids
1557 .iter()
1558 .map(|id| StoreWalAction::DeleteEntityRecord {
1559 collection: collection.to_string(),
1560 entity_id: id.raw(),
1561 })
1562 .collect::<Vec<_>>();
1563 self.finish_paged_write(actions)?;
1564
1565 if updated_tree {
1566 if let Some(before) = before {
1567 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1568 }
1569 self.flush_paged_registry()?;
1574 }
1575 Ok(deleted_ids)
1576 }
1577
1578 pub fn set_metadata(
1580 &self,
1581 collection: &str,
1582 id: EntityId,
1583 metadata: Metadata,
1584 ) -> Result<(), StoreError> {
1585 let manager = self
1586 .get_collection(collection)
1587 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1588
1589 manager.set_metadata(id, metadata)?;
1590 if let Some(entity) = manager.get(id) {
1591 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1592 }
1593 Ok(())
1594 }
1595
1596 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1598 self.get_collection(collection)?.get_metadata(id)
1599 }
1600
1601 pub fn try_get_metadata(
1603 &self,
1604 collection: &str,
1605 id: EntityId,
1606 limit_bytes: usize,
1607 ) -> Result<Option<Metadata>, StoreError> {
1608 let Some(manager) = self.get_collection(collection) else {
1609 return Ok(None);
1610 };
1611 manager.try_get_metadata(id, limit_bytes)
1612 }
1613
1614 pub fn add_cross_ref(
1616 &self,
1617 source_collection: &str,
1618 source_id: EntityId,
1619 target_collection: &str,
1620 target_id: EntityId,
1621 ref_type: RefType,
1622 weight: f32,
1623 ) -> Result<(), StoreError> {
1624 let source_collection = Self::canonical_collection(source_collection);
1625 let target_collection = Self::canonical_collection(target_collection);
1626 let registry_dirty = self.resident.add_cross_ref_in_memory(
1627 source_collection,
1628 source_id,
1629 target_collection,
1630 target_id,
1631 ref_type,
1632 weight,
1633 self.config.max_cross_refs,
1634 |entity| {
1635 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1636 },
1637 )?;
1638 if registry_dirty
1639 && matches!(
1640 self.config.durability_mode,
1641 crate::api::DurabilityMode::Strict
1642 )
1643 {
1644 self.flush_paged_state()?;
1645 }
1646 Ok(())
1647 }
1648
1649 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1651 self.resident.get_refs_from(id)
1652 }
1653
1654 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1656 self.resident.get_refs_to(id)
1657 }
1658
1659 pub fn expand_refs(
1661 &self,
1662 id: EntityId,
1663 depth: u32,
1664 ref_types: Option<&[RefType]>,
1665 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1666 let mut results = Vec::new();
1667 let mut visited = std::collections::HashSet::new();
1668 visited.insert(id);
1669
1670 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1671
1672 results
1673 }
1674
1675 fn expand_refs_recursive(
1676 &self,
1677 id: EntityId,
1678 max_depth: u32,
1679 ref_types: Option<&[RefType]>,
1680 visited: &mut std::collections::HashSet<EntityId>,
1681 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1682 current_depth: u32,
1683 ) {
1684 if current_depth > max_depth {
1685 return;
1686 }
1687
1688 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1689 if visited.contains(&target_id) {
1690 continue;
1691 }
1692
1693 if let Some(types) = ref_types {
1694 if !types.contains(&ref_type) {
1695 continue;
1696 }
1697 }
1698
1699 visited.insert(target_id);
1700
1701 if let Some(entity) = self.get(&target_collection, target_id) {
1702 results.push((entity, current_depth, ref_type));
1703
1704 self.expand_refs_recursive(
1706 target_id,
1707 max_depth,
1708 ref_types,
1709 visited,
1710 results,
1711 current_depth + 1,
1712 );
1713 }
1714 }
1715 }
1716
1717 pub(crate) fn index_cross_refs(
1719 &self,
1720 entity: &UnifiedEntity,
1721 collection: &str,
1722 ) -> Result<(), StoreError> {
1723 self.resident.index_cross_refs(entity, collection)
1724 }
1725
1726 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1728 self.resident.unindex_cross_refs(id)
1729 }
1730
1731 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1732 self.resident.unindex_cross_refs_batch(ids)
1733 }
1734
1735 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1739 self.resident.unindex_cross_refs_fast_path_hits()
1740 }
1741
1742 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1744 where
1745 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1746 {
1747 self.resident.query_all(filter)
1748 }
1749
1750 pub fn scan<F>(
1752 &self,
1753 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1754 filter: F,
1755 ) -> Vec<(String, UnifiedEntity)>
1756 where
1757 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1758 {
1759 self.resident.scan(snapshot, filter)
1760 }
1761
1762 pub fn filter_metadata_all(
1764 &self,
1765 filters: &[(String, MetadataFilter)],
1766 ) -> Vec<(String, EntityId)> {
1767 self.resident.filter_metadata_all(filters)
1768 }
1769
1770 pub fn stats(&self) -> StoreStats {
1772 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1778 let collections = self.resident.collections.read();
1779 collections
1780 .iter()
1781 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1782 .collect()
1783 };
1784
1785 let mut stats = StoreStats {
1786 collection_count: pairs.len(),
1787 ..Default::default()
1788 };
1789
1790 for (name, manager) in &pairs {
1791 let manager_stats = manager.stats();
1792 stats.total_entities += manager_stats.total_entities;
1793 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1794 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1795 stats.collections.insert(name.clone(), manager_stats);
1796 }
1797
1798 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1799 stats.total_memory_bytes += self
1800 .checkpoint_versions
1801 .read()
1802 .values()
1803 .filter_map(|version| version.deferred_rows.as_ref())
1804 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1805 .sum::<usize>();
1806 stats
1807 }
1808
1809 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1811 let collections = self.resident.collections.read();
1812 for manager in collections.values() {
1813 manager.run_maintenance()?;
1814 }
1815 Ok(())
1816 }
1817
1818 pub fn reclaimable_segment_bytes(&self) -> u64 {
1820 let collections = self.resident.collections.read();
1821 collections
1822 .values()
1823 .map(|manager| manager.reclaimable_bytes())
1824 .fold(0, u64::saturating_add)
1825 }
1826
1827 pub fn pressure_consolidation_tick(&self) -> bool {
1830 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1831 let mut advanced = false;
1834 for manager in &managers {
1835 advanced |= manager.pressure_consolidation_tick();
1836 }
1837 advanced
1838 }
1839}
1840
1841fn flatten_config_json(
1843 prefix: &str,
1844 value: &crate::serde_json::Value,
1845 out: &mut Vec<(String, reddb_types::Value)>,
1846) {
1847 use reddb_types::Value;
1848 match value {
1849 crate::serde_json::Value::Object(map) => {
1850 for (k, v) in map {
1851 let key = if prefix.is_empty() {
1852 k.clone()
1853 } else {
1854 format!("{prefix}.{k}")
1855 };
1856 flatten_config_json(&key, v, out);
1857 }
1858 }
1859 crate::serde_json::Value::String(s) => {
1860 out.push((prefix.to_string(), Value::text(s.clone())));
1861 }
1862 crate::serde_json::Value::Integer(n) => {
1863 out.push((prefix.to_string(), Value::Integer(*n)));
1864 }
1865 crate::serde_json::Value::Number(n) => {
1866 if n.fract().abs() < f64::EPSILON {
1867 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1868 } else {
1869 out.push((prefix.to_string(), Value::Float(*n)));
1870 }
1871 }
1872 crate::serde_json::Value::Decimal(n) => {
1873 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1874 }
1875 crate::serde_json::Value::Bool(b) => {
1876 out.push((prefix.to_string(), Value::Boolean(*b)));
1877 }
1878 crate::serde_json::Value::Null => {
1879 out.push((prefix.to_string(), Value::Null));
1880 }
1881 crate::serde_json::Value::Array(_) => {
1882 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1883 out.push((prefix.to_string(), Value::text(json_str)));
1884 }
1885 }
1886}