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