1use super::*;
2
3impl UnifiedStore {
4 pub(crate) fn insert_vault_version(
8 &self,
9 collection: &str,
10 entity: UnifiedEntity,
11 metadata: Metadata,
12 ) -> Result<(), StoreError> {
13 assert!(matches!(entity.data, EntityData::Row(_)));
14 let manager = self
15 .get_collection(collection)
16 .ok_or_else(|| StoreError::CollectionNotFound(collection.into()))?;
17 self.pending_vault_versions.write().insert(entity.id);
18 let id = manager.insert(entity)?;
19 self.register_entity_id(id);
20 if !metadata.fields.is_empty() {
21 manager.set_metadata(id, metadata)?;
22 }
23 let entity = manager
24 .get(id)
25 .expect("inserted vault version remains in memory");
26 self.persist_entities_to_pager_wal_only(collection, &[entity])
27 }
28
29 pub(crate) fn persist_entities_to_pager(
30 &self,
31 collection: &str,
32 entities: &[UnifiedEntity],
33 ) -> Result<(), StoreError> {
34 self.persist_entities_impl(collection, entities, false)
35 }
36
37 pub(crate) fn persist_entity_refs_to_pager(
41 &self,
42 collection: &str,
43 entities: &[&UnifiedEntity],
44 ) -> Result<(), StoreError> {
45 self.persist_entity_refs_impl(collection, entities, false)
46 }
47
48 pub(crate) fn persist_entity_refs_to_pager_wal_only(
51 &self,
52 collection: &str,
53 entities: &[&UnifiedEntity],
54 ) -> Result<(), StoreError> {
55 self.persist_entity_refs_impl(collection, entities, true)
56 }
57
58 fn persist_entity_refs_impl(
59 &self,
60 collection: &str,
61 entities: &[&UnifiedEntity],
62 skip_btree: bool,
63 ) -> Result<(), StoreError> {
64 if entities.is_empty() {
65 return Ok(());
66 }
67 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
68 return Ok(());
69 }
70 let fv = self.format_version();
71 let manager = self
72 .get_collection(collection)
73 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
74 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
75 .iter()
76 .map(|entity| {
77 let metadata = manager.get_metadata(entity.id);
78 (
79 entity.id.raw().to_be_bytes().to_vec(),
80 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
81 )
82 })
83 .collect();
84 serialized.sort_by(|a, b| a.0.cmp(&b.0));
85
86 if !skip_btree && !Self::deferred_store_wal_capture_active() {
87 let Some(pager) = &self.pager else {
88 let records: Vec<Vec<u8>> =
89 serialized.into_iter().map(|(_id, record)| record).collect();
90 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
91 collection: collection.to_string(),
92 records,
93 }])?;
94 return Ok(());
95 };
96 let mut btree_indices = self.btree_indices.write();
97 let btree = btree_indices
98 .entry(collection.to_string())
99 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
100 let root_before = btree.root_page_id();
101 btree.upsert_batch_sorted(&serialized).map_err(|e| {
102 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
103 })?;
104 let root_after = btree.root_page_id();
105 drop(btree_indices);
106 if root_before != root_after {
107 self.mark_paged_registry_dirty();
108 }
109 }
110 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
111 self.finish_paged_write_for_projection(
112 [StoreWalAction::BulkUpsertEntityRecords {
113 collection: collection.to_string(),
114 records,
115 }],
116 skip_btree,
117 )?;
118 Ok(())
119 }
120
121 pub(crate) fn persist_entities_to_pager_wal_only(
133 &self,
134 collection: &str,
135 entities: &[UnifiedEntity],
136 ) -> Result<(), StoreError> {
137 self.persist_entities_impl(collection, entities, true)
138 }
139
140 fn persist_entities_impl(
141 &self,
142 collection: &str,
143 entities: &[UnifiedEntity],
144 skip_btree: bool,
145 ) -> Result<(), StoreError> {
146 if entities.is_empty() {
147 return Ok(());
148 }
149
150 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
151 return Ok(());
152 }
153
154 let fv = self.format_version();
155 let manager = self
156 .get_collection(collection)
157 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
158 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
159 .iter()
160 .map(|entity| {
161 let metadata = manager.get_metadata(entity.id);
162 (
163 entity.id.raw().to_be_bytes().to_vec(),
164 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
165 )
166 })
167 .collect();
168 serialized.sort_by(|a, b| a.0.cmp(&b.0));
170
171 if !skip_btree && !Self::deferred_store_wal_capture_active() {
172 let Some(pager) = &self.pager else {
173 let records: Vec<Vec<u8>> =
174 serialized.into_iter().map(|(_id, record)| record).collect();
175 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
176 collection: collection.to_string(),
177 records,
178 }])?;
179 return Ok(());
180 };
181 let mut btree_indices = self.btree_indices.write();
182 let btree = btree_indices
183 .entry(collection.to_string())
184 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
185 let root_before = btree.root_page_id();
186
187 btree.upsert_batch_sorted(&serialized).map_err(|e| {
191 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
192 })?;
193 let root_after = btree.root_page_id();
194 drop(btree_indices);
195 if root_before != root_after {
196 self.mark_paged_registry_dirty();
197 }
198 }
199 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
209 self.finish_paged_write_for_projection(
210 [StoreWalAction::BulkUpsertEntityRecords {
211 collection: collection.to_string(),
212 records,
213 }],
214 skip_btree,
215 )?;
216
217 Ok(())
218 }
219
220 pub(crate) fn update_graph_label_index(
222 &self,
223 collection: &str,
224 label: &str,
225 entity_id: EntityId,
226 ) {
227 self.resident
228 .update_graph_label_index(collection, label, entity_id)
229 }
230
231 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
233 self.resident
234 .remove_from_graph_label_index(collection, entity_id)
235 }
236
237 pub(crate) fn remove_from_graph_label_index_batch(
238 &self,
239 collection: &str,
240 entity_ids: &[EntityId],
241 ) {
242 self.resident
243 .remove_from_graph_label_index_batch(collection, entity_ids)
244 }
245
246 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
248 self.resident.lookup_graph_nodes_by_label(label)
249 }
250
251 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
253 self.resident
254 .lookup_graph_nodes_by_label_in(collection, label)
255 }
256
257 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
258 let name = name.into();
259 let mut collections = self.resident.collections.write();
260
261 if collections.contains_key(&name) {
262 return Err(StoreError::CollectionExists(name));
263 }
264
265 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
266 collections.insert(name.clone(), Arc::new(manager));
267 drop(collections);
268
269 let create = match self.prepare_operational_collection_create(&name) {
270 Ok(create) => create,
271 Err(err) => {
272 let mut collections = self.resident.collections.write();
273 collections.remove(&name);
274 return Err(err);
275 }
276 };
277 if let Some(create) = create {
278 if let Err(err) = create.publish().map_err(StoreError::Io) {
279 let mut collections = self.resident.collections.write();
280 collections.remove(&name);
281 return Err(err);
282 }
283 }
284
285 self.mark_paged_registry_dirty();
286 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
287
288 Ok(())
289 }
290
291 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
293 self.resident
294 .get_or_create_collection(name, &self.config.manager_config)
295 }
296
297 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
299 self.resident.get_collection(name)
300 }
301
302 pub fn context_index(&self) -> &ContextIndex {
304 self.resident.context_index()
305 }
306
307 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
313 let mut map = self.replayed_turbo_inserts.lock();
314 map.remove(collection)
315 }
316
317 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
318 let mut deltas = self.replayed_probabilistic_deltas.lock();
319 std::mem::take(&mut *deltas)
320 }
321
322 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
329 let manager = self.get_or_create_collection("red_config");
330 let mut pairs = Vec::new();
331 flatten_config_json(prefix, json, &mut pairs);
332 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
334 .iter()
335 .map(|(key, _)| (key.clone(), Vec::new()))
336 .collect();
337 for entity in manager.query_all(|_| true) {
338 if let EntityData::Row(row) = &entity.data {
339 let Some(named) = row.named.as_ref() else {
340 continue;
341 };
342 if let Some(reddb_types::Value::Text(key)) = named.get("key") {
343 if let Some(rows) = previous.get_mut(key.as_ref()) {
344 rows.push((entity.id, named.get("value").cloned()));
345 }
346 }
347 }
348 }
349 let mut saved = 0;
350 for (key, value) in pairs {
351 let stale = previous.remove(&key).unwrap_or_default();
352 if let [(_, Some(current))] = stale.as_slice() {
356 if *current == value {
357 saved += 1;
358 continue;
359 }
360 }
361 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
362 let entity = UnifiedEntity::new(
366 EntityId::new(0),
367 EntityKind::TableRow {
368 table: Arc::from("red_config"),
369 row_id: 0,
370 },
371 EntityData::Row(RowData {
372 columns: Vec::new(),
373 named: Some(
374 [
375 ("key".to_string(), reddb_types::Value::text(key)),
376 ("value".to_string(), value),
377 ]
378 .into_iter()
379 .collect(),
380 ),
381 schema: None,
382 }),
383 );
384 if self.insert_auto("red_config", entity).is_ok() {
385 saved += 1;
386 if !stale.is_empty() {
387 let _ = self.delete_batch("red_config", &stale);
388 }
389 }
390 }
391 saved
392 }
393
394 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
398 let manager = self.get_collection("red_config")?;
399 let mut latest: Option<(u64, reddb_types::Value)> = None;
400 for entity in manager.query_all(|_| true) {
401 if let EntityData::Row(row) = &entity.data {
402 if let Some(named) = &row.named {
403 let key_matches = named
404 .get("key")
405 .and_then(|v| match v {
406 reddb_types::Value::Text(s) => Some(s.as_ref() == key),
407 _ => None,
408 })
409 .unwrap_or(false);
410 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
411 if let Some(value) = named.get("value") {
412 latest = Some((entity.id.raw(), value.clone()));
413 }
414 }
415 }
416 }
417 }
418 latest.map(|(_, value)| value)
419 }
420
421 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
426 self.resident.set_aux_metadata(bytes)
427 }
428
429 pub fn aux_metadata(&self) -> Vec<u8> {
431 self.resident.aux_metadata()
432 }
433
434 pub fn list_collections(&self) -> Vec<String> {
436 self.resident.list_collections()
437 }
438
439 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
441 let _vault_guard = self.vault_publication_lock.lock();
442 let drop = self.prepare_operational_collection_drop(name)?;
443 let manager = {
444 let mut collections = self.resident.collections.write();
445
446 collections
447 .remove(name)
448 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
449 };
450
451 let entities = manager.query_all(|_| true);
452 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
453
454 for entity_id in &entity_ids {
455 self.resident.context_index.remove_entity(*entity_id);
456 let _ = self.unindex_cross_refs(*entity_id);
457 }
458
459 if let Some(tree) = self.btree_indices.write().remove(name) {
460 self.retire_btree(tree);
461 }
462
463 self.resident
464 .entity_cache
465 .retain(|entity_id, (collection, _)| {
466 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
467 });
468
469 self.resident.cross_refs.write().retain(|source_id, refs| {
470 refs.retain(|(target_id, _, target_collection)| {
471 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
472 });
473 !entity_ids.iter().any(|id| id == source_id)
474 });
475
476 self.resident
477 .reverse_refs
478 .write()
479 .retain(|target_id, refs| {
480 refs.retain(|(source_id, _, source_collection)| {
481 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
482 });
483 !entity_ids.iter().any(|id| id == target_id)
484 });
485
486 self.mark_paged_registry_dirty();
487 self.finish_paged_write([StoreWalAction::DropCollection {
488 name: name.to_string(),
489 }])?;
490 if let Some(drop) = drop {
491 drop.finish().map_err(StoreError::Io)?;
492 }
493
494 Ok(())
495 }
496
497 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
499 let manager = self
500 .get_collection(collection)
501 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
502
503 let mut entity = entity;
504 if entity.id.raw() == 0 {
505 entity.id = self.next_entity_id();
506 } else {
507 self.register_entity_id(entity.id);
508 }
509 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
511 if *row_id == 0 {
512 *row_id = manager.next_row_id();
513 } else {
514 manager.register_row_id(*row_id);
515 }
516 }
517 entity.ensure_table_logical_id();
518 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
520 {
521 Some(node.label.clone())
522 } else {
523 None
524 };
525
526 let id = manager.insert(entity)?;
527 self.register_entity_id(id);
528
529 if let Some(ref label) = graph_node_label {
531 self.update_graph_label_index(collection, label, id);
532 }
533
534 let mut registry_dirty = false;
537 if let Some(pager) = self
538 .pager
539 .as_ref()
540 .filter(|_| !Self::deferred_store_wal_capture_active())
541 {
542 if let Some(entity) = manager.get(id) {
543 let mut btree_indices = self.btree_indices.write();
544 let btree = btree_indices
545 .entry(collection.to_string())
546 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
547 let root_before = btree.root_page_id();
548
549 let key = id.raw().to_be_bytes();
550 let metadata = manager.get_metadata(id);
551 let value = Self::serialize_entity_record(
552 &entity,
553 metadata.as_ref(),
554 self.format_version(),
555 );
556 let _ = btree.insert(&key, &value);
558 registry_dirty = root_before != btree.root_page_id();
559 }
560 }
561
562 if self.config.auto_index_refs {
564 if let Some(entity) = manager.get(id) {
565 self.index_cross_refs(&entity, collection)?;
566 }
567 }
568
569 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
573 let actions = manager
574 .get(id)
575 .map(|entity| {
576 let metadata = manager.get_metadata(id);
577 vec![StoreWalAction::upsert_entity(
578 collection,
579 &entity,
580 metadata.as_ref(),
581 self.format_version(),
582 )]
583 })
584 .unwrap_or_default();
585 if registry_dirty {
586 self.mark_paged_registry_dirty();
587 }
588 self.finish_paged_write(actions)?;
589 }
590
591 Ok(id)
592 }
593
594 pub fn bulk_insert(
599 &self,
600 collection: &str,
601 mut entities: Vec<UnifiedEntity>,
602 ) -> Result<Vec<EntityId>, StoreError> {
603 let _publication = self.vault_publication_lock.lock();
606 let trace = matches!(
610 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
611 Some("1") | Some("true") | Some("on")
612 );
613 let t_start = std::time::Instant::now();
614 let n = entities.len();
615 let manager = self.get_or_create_collection(collection);
616 let t_get_coll = t_start.elapsed();
617
618 let t0 = std::time::Instant::now();
627 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
628 let n_missing_row_ids = entities
629 .iter()
630 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
631 .count() as u64;
632 let mut entity_id_range = if n_missing_entity_ids > 0 {
633 self.reserve_entity_ids(n_missing_entity_ids)
634 } else {
635 0..0
636 };
637 let mut row_id_range = if n_missing_row_ids > 0 {
638 manager.reserve_row_ids(n_missing_row_ids)
639 } else {
640 0..0
641 };
642 for entity in &mut entities {
643 if entity.id.raw() == 0 {
644 let next = entity_id_range
645 .next()
646 .expect("reserved entity-id range exhausted");
647 entity.id = EntityId::new(next);
648 } else {
649 self.register_entity_id(entity.id);
650 }
651 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
652 if *row_id == 0 {
653 *row_id = row_id_range
654 .next()
655 .expect("reserved row-id range exhausted");
656 } else {
657 manager.register_row_id(*row_id);
658 }
659 }
660 entity.ensure_table_logical_id();
661 }
662 let t_assign_ids = t0.elapsed();
663
664 let graph_labels: Vec<Option<(String, EntityId)>> = entities
666 .iter()
667 .map(|e| {
668 if let EntityKind::GraphNode(ref node) = e.kind {
669 Some((node.label.clone(), e.id))
670 } else {
671 None
672 }
673 })
674 .collect();
675
676 let t0 = std::time::Instant::now();
679 let ids = manager.bulk_insert(entities)?;
680 let t_manager = t0.elapsed();
681 for id in &ids {
682 self.register_entity_id(*id);
683 }
684
685 let t0 = std::time::Instant::now();
692 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
693 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
694 let fv = self.format_version();
695 let serial_map = |id: &EntityId| {
696 manager
697 .get_with(*id, |entity| {
698 (
699 id.raw().to_be_bytes().to_vec(),
700 Self::serialize_entity_record(entity, None, fv),
701 )
702 })
703 .expect("bulk-inserted entity must remain in the fenced segment")
704 };
705 if ids.len() >= 512 {
711 use rayon::prelude::*;
712 Some(ids.par_iter().map(serial_map).collect())
713 } else {
714 Some(ids.iter().map(serial_map).collect())
715 }
716 } else {
717 None
718 };
719 let t_serialize = t0.elapsed();
720
721 for (label, entity_id) in graph_labels.iter().flatten() {
723 self.update_graph_label_index(collection, label, *entity_id);
724 }
725
726 let skip_btree_requested = matches!(
736 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
737 .ok()
738 .as_deref(),
739 Some("1") | Some("true") | Some("on")
740 );
741 let skip_btree = skip_btree_requested && self.pager.is_none();
744 if skip_btree_requested && !skip_btree {
745 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
747 IGNORED.get_or_init(|| {
748 tracing::warn!(
749 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
750 active — flag ignored; bulk inserts will be persisted normally"
751 );
752 });
753 } else if skip_btree {
754 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
756 WARNED.get_or_init(|| {
757 tracing::warn!(
758 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
759 bulk inserts NOT durable; data will be lost on restart"
760 );
761 });
762 }
763
764 let mut t_btree_lock = std::time::Duration::ZERO;
768 let mut t_btree_insert = std::time::Duration::ZERO;
769 let mut t_flush = std::time::Duration::ZERO;
770 if !skip_btree && !Self::deferred_store_wal_capture_active() {
771 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
772 let t0 = std::time::Instant::now();
773 let mut btree_indices = self.btree_indices.write();
774 let btree = btree_indices
775 .entry(collection.to_string())
776 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
777 let root_before = btree.root_page_id();
778 t_btree_lock = t0.elapsed();
779
780 let t0 = std::time::Instant::now();
781 let _ = btree.bulk_insert_sorted(batch);
782 t_btree_insert = t0.elapsed();
783 let registry_dirty = root_before != btree.root_page_id();
784
785 let t0 = std::time::Instant::now();
786 if registry_dirty {
787 self.mark_paged_registry_dirty();
788 }
789 t_flush = t0.elapsed();
790 }
791 }
792
793 let actions = serialized
799 .map(|batch| {
800 let records: Vec<Vec<u8>> =
801 batch.into_iter().map(|(_key, record)| record).collect();
802 vec![StoreWalAction::BulkUpsertEntityRecords {
803 collection: collection.to_string(),
804 records,
805 }]
806 })
807 .unwrap_or_default();
808 self.finish_paged_write(actions)?;
809
810 if trace {
811 tracing::debug!(
812 n,
813 total = ?t_start.elapsed(),
814 get_coll = ?t_get_coll,
815 assign = ?t_assign_ids,
816 serialize = ?t_serialize,
817 manager = ?t_manager,
818 btree_lock = ?t_btree_lock,
819 btree = ?t_btree_insert,
820 flush = ?t_flush,
821 "bulk_insert timing"
822 );
823 }
824
825 Ok(ids)
826 }
827
828 pub fn insert_auto(
830 &self,
831 collection: &str,
832 entity: UnifiedEntity,
833 ) -> Result<EntityId, StoreError> {
834 let _publication = self.vault_publication_lock.lock();
836 let manager = self.get_or_create_collection(collection);
837 let mut entity = entity;
838 if entity.id.raw() == 0 {
839 entity.id = self.next_entity_id();
840 } else {
841 self.register_entity_id(entity.id);
842 }
843 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
845 if *row_id == 0 {
846 *row_id = manager.next_row_id();
847 } else {
848 manager.register_row_id(*row_id);
849 }
850 }
851 entity.ensure_table_logical_id();
852
853 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
855 {
856 Some(node.label.clone())
857 } else {
858 None
859 };
860 self.resident
862 .context_index
863 .index_entity(collection, &entity);
864
865 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
868 if self.config.auto_index_refs {
869 self.index_cross_refs(&entity, collection)?;
870 }
871
872 let paged_btree = if persist_record && !Self::deferred_store_wal_capture_active() {
879 self.get_or_create_btree(collection)
880 } else {
881 None
882 };
883 let persist_fence = paged_btree
884 .as_ref()
885 .map(|_| self.btree_indices.read_recursive());
886 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
887 let unversioned = entity.xmin == 0 && entity.xmax == 0;
888
889 let id = manager.insert(entity)?;
890 let serialized_record = if persist_record {
891 Some(
892 manager
893 .get_with(id, |entity| {
894 Self::serialize_entity_record(entity, None, self.format_version())
895 })
896 .ok_or(StoreError::EntityNotFound(id))?,
897 )
898 } else {
899 None
900 };
901 if let Some(ref label) = graph_node_label {
909 self.update_graph_label_index(collection, label, id);
910 }
911
912 let mut registry_dirty = false;
913 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
914 let root_before = btree.root_page_id();
915
916 let key = id.raw().to_be_bytes();
917 btree.insert(&key, record).map_err(|e| {
918 StoreError::Io(std::io::Error::other(format!(
919 "B-tree insert error while inserting '{collection}'/{id}: {e}"
920 )))
921 })?;
922 registry_dirty = root_before != btree.root_page_id();
923 }
924 drop(persist_fence);
925
926 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
930 let actions = serialized_record
931 .map(|record| {
932 vec![StoreWalAction::UpsertEntityRecord {
933 collection: collection.to_string(),
934 record,
935 }]
936 })
937 .unwrap_or_default();
938 if registry_dirty {
939 self.mark_paged_registry_dirty();
940 }
941 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
942 drop(_publication);
943 self.complete_grouped_actions(&pending, &actions)?;
944 } else {
945 self.finish_paged_write(actions)?;
946 }
947 }
948 if unversioned && !Self::deferred_store_wal_capture_active() {
949 if let Some(before) = before {
950 self.note_paged_insert(collection, &manager, &before, id);
951 }
952 }
953 Ok(id)
954 }
955
956 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
962 if let Some(entity) = self
964 .get_collection(collection)
965 .and_then(|manager| manager.get(id))
966 {
967 return Some(entity);
968 }
969
970 if self.pager.is_some() {
972 let btree_indices = self.btree_indices.read();
973 if let Some(btree) = btree_indices.get(collection) {
974 let key = id.raw().to_be_bytes();
975 if let Ok(Some(value)) = btree.get(&key) {
976 if let Ok((entity, _)) =
977 Self::deserialize_entity_record(&value, self.format_version())
978 {
979 return Some(entity);
980 }
981 }
982 }
983 }
984
985 None
986 }
987
988 pub fn get_table_row_by_logical_id(
994 &self,
995 collection: &str,
996 logical_id: EntityId,
997 ) -> Option<UnifiedEntity> {
998 if let Some(entity) = self.get(collection, logical_id) {
999 if matches!(entity.kind, EntityKind::TableRow { .. })
1000 && entity.logical_id() == logical_id
1001 && entity.xmax == 0
1002 {
1003 return Some(entity);
1004 }
1005 }
1006
1007 let manager = self.get_collection(collection)?;
1008 let mut matches = manager.query_all(|entity| {
1009 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1010 });
1011 matches
1012 .iter()
1013 .find(|entity| entity.xmax == 0)
1014 .cloned()
1015 .or_else(|| matches.pop())
1016 }
1017
1018 pub fn table_row_versions_by_logical_id(
1019 &self,
1020 collection: &str,
1021 logical_id: EntityId,
1022 ) -> Vec<UnifiedEntity> {
1023 self.resident
1024 .table_row_versions_by_logical_id(collection, logical_id)
1025 }
1026
1027 pub fn vacuum_mvcc_history(
1028 &self,
1029 collection: &str,
1030 cutoff_xid: u64,
1031 ) -> Result<MvccVacuumStats, StoreError> {
1032 let manager = self
1033 .get_collection(collection)
1034 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1035 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1036 manager.for_each_entity(|entity| {
1037 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1038 return true;
1039 }
1040 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1041 if entity.xmax == 0 {
1042 entry.0 = true;
1043 }
1044 entry.1 = entry.1.max(entity.xmin);
1045 true
1046 });
1047
1048 let mut stats = MvccVacuumStats::default();
1049 let mut reclaim_ids = Vec::new();
1050 manager.for_each_entity(|entity| {
1051 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1052 return true;
1053 }
1054 stats.scanned_versions += 1;
1055 let (has_live_version, max_xmin) = logical
1056 .get(&entity.logical_id())
1057 .copied()
1058 .unwrap_or((false, entity.xmin));
1059 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1060 if entity.xmax < cutoff_xid {
1061 stats.reclaimed_versions += 1;
1062 if is_delete_tombstone {
1063 stats.reclaimed_tombstones += 1;
1064 } else {
1065 stats.reclaimed_history_versions += 1;
1066 }
1067 reclaim_ids.push(entity.id);
1068 } else {
1069 stats.retained_versions += 1;
1070 if is_delete_tombstone {
1071 stats.retained_tombstones += 1;
1072 } else {
1073 stats.retained_history_versions += 1;
1074 }
1075 }
1076 true
1077 });
1078
1079 if !reclaim_ids.is_empty() {
1080 self.delete_batch(collection, &reclaim_ids)?;
1081 }
1082 Ok(stats)
1083 }
1084
1085 pub(crate) fn install_versioned_table_row_update(
1086 &self,
1087 collection: &str,
1088 old_version: UnifiedEntity,
1089 mut new_version: UnifiedEntity,
1090 metadata: Option<&Metadata>,
1091 ) -> Result<(), StoreError> {
1092 let manager = self
1093 .get_collection(collection)
1094 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1095
1096 let old_id = old_version.id;
1097 let new_id = new_version.id;
1098 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1099
1100 self.resident.entity_cache.remove(old_id.raw());
1101 self.resident.entity_cache.remove(new_id.raw());
1102 manager.update(old_version.clone())?;
1103 self.resident.context_index.remove_entity(old_id);
1104
1105 self.register_entity_id(new_version.id);
1106 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1107 if *row_id == 0 {
1108 *row_id = manager.next_row_id();
1109 } else {
1110 manager.register_row_id(*row_id);
1111 }
1112 }
1113 new_version.ensure_table_logical_id();
1114 manager.insert(new_version.clone())?;
1115 if let Some(metadata) = inherited_metadata {
1116 manager.set_metadata(new_id, metadata)?;
1117 }
1118 self.resident
1119 .context_index
1120 .index_entity(collection, &new_version);
1121 if self.config.auto_index_refs {
1122 self.index_cross_refs(&new_version, collection)?;
1123 }
1124
1125 let old_metadata = manager.get_metadata(old_id);
1126 let new_metadata = manager.get_metadata(new_id);
1127 let fv = self.format_version();
1128 let records = vec![
1131 manager
1132 .get_with(old_id, |entity| {
1133 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1134 })
1135 .ok_or(StoreError::EntityNotFound(old_id))?,
1136 manager
1137 .get_with(new_id, |entity| {
1138 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1139 })
1140 .ok_or(StoreError::EntityNotFound(new_id))?,
1141 ];
1142 self.finish_paged_write_for_projection(
1143 [StoreWalAction::BulkUpsertEntityRecords {
1144 collection: collection.to_string(),
1145 records,
1146 }],
1147 true,
1148 )?;
1149
1150 Ok(())
1151 }
1152
1153 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1159 match self.get_collection(collection) {
1160 Some(manager) => manager.get_many(ids),
1161 None => vec![None; ids.len()],
1162 }
1163 }
1164
1165 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1167 self.resident.get_any(id)
1168 }
1169
1170 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1176 self.resident.entity_cache_hit_rate()
1177 }
1178
1179 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1181 self.resident.entity_cache_stats()
1182 }
1183
1184 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1186 let publication = self.vault_publication_lock.lock();
1187 self.resident.entity_cache.remove(id.raw());
1189 let manager = self
1190 .get_collection(collection)
1191 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1192
1193 let deleted = manager.delete(id)?;
1194 if !deleted {
1195 return Ok(false);
1196 }
1197
1198 let mut registry_dirty = false;
1200 if self.pager.is_some() {
1201 let btree_indices = self.btree_indices.read();
1202 if let Some(btree) = btree_indices.get(collection) {
1203 let root_before = btree.root_page_id();
1204 let key = id.raw().to_be_bytes();
1205 let _ = btree.delete(&key);
1206 registry_dirty = root_before != btree.root_page_id();
1207 }
1208 }
1209
1210 self.unindex_cross_refs(id)?;
1212
1213 self.remove_from_graph_label_index(collection, id);
1215
1216 if registry_dirty {
1217 self.mark_paged_registry_dirty();
1218 }
1219 let actions = [StoreWalAction::DeleteEntityRecord {
1220 collection: collection.to_string(),
1221 entity_id: id.raw(),
1222 }];
1223 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1224 drop(publication);
1225 self.complete_grouped_actions(&pending, &actions)?;
1226 } else {
1227 self.finish_paged_write(actions)?;
1228 }
1229
1230 Ok(true)
1231 }
1232
1233 pub fn delete_batch(
1234 &self,
1235 collection: &str,
1236 ids: &[EntityId],
1237 ) -> Result<Vec<EntityId>, StoreError> {
1238 if ids.is_empty() {
1239 return Ok(Vec::new());
1240 }
1241 let _publication = self.vault_publication_lock.lock();
1244
1245 self.resident
1250 .entity_cache
1251 .remove_many(ids.iter().map(|id| id.raw()));
1252
1253 let manager = self
1254 .get_collection(collection)
1255 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1256
1257 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1258 let deleted_ids = manager.delete_batch(ids)?;
1259 if deleted_ids.is_empty() {
1260 return Ok(deleted_ids);
1261 }
1262
1263 let mut registry_dirty = false;
1264 let mut updated_tree = false;
1265 if self.pager.is_some() {
1266 let btree_indices = self.btree_indices.read();
1267 if let Some(btree) = btree_indices.get(collection) {
1268 updated_tree = true;
1269 let root_before = btree.root_page_id();
1270 for id in &deleted_ids {
1271 let key = id.raw().to_be_bytes();
1272 updated_tree &= btree.delete(&key).is_ok();
1275 }
1276 registry_dirty = root_before != btree.root_page_id();
1277 }
1278 }
1279
1280 self.unindex_cross_refs_batch(&deleted_ids)?;
1281 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1282 if registry_dirty {
1283 self.mark_paged_registry_dirty();
1284 }
1285 let actions = deleted_ids
1286 .iter()
1287 .map(|id| StoreWalAction::DeleteEntityRecord {
1288 collection: collection.to_string(),
1289 entity_id: id.raw(),
1290 })
1291 .collect::<Vec<_>>();
1292 self.finish_paged_write(actions)?;
1293
1294 if updated_tree {
1295 if let Some(before) = before {
1296 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1297 }
1298 self.flush_paged_registry()?;
1303 }
1304 Ok(deleted_ids)
1305 }
1306
1307 pub fn set_metadata(
1309 &self,
1310 collection: &str,
1311 id: EntityId,
1312 metadata: Metadata,
1313 ) -> Result<(), StoreError> {
1314 let manager = self
1315 .get_collection(collection)
1316 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1317
1318 manager.set_metadata(id, metadata)?;
1319 if let Some(entity) = manager.get(id) {
1320 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1321 }
1322 Ok(())
1323 }
1324
1325 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1327 self.get_collection(collection)?.get_metadata(id)
1328 }
1329
1330 pub fn add_cross_ref(
1332 &self,
1333 source_collection: &str,
1334 source_id: EntityId,
1335 target_collection: &str,
1336 target_id: EntityId,
1337 ref_type: RefType,
1338 weight: f32,
1339 ) -> Result<(), StoreError> {
1340 let registry_dirty = self.resident.add_cross_ref_in_memory(
1341 source_collection,
1342 source_id,
1343 target_collection,
1344 target_id,
1345 ref_type,
1346 weight,
1347 self.config.max_cross_refs,
1348 |entity| {
1349 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1350 },
1351 )?;
1352 if registry_dirty
1353 && matches!(
1354 self.config.durability_mode,
1355 crate::api::DurabilityMode::Strict
1356 )
1357 {
1358 self.flush_paged_state()?;
1359 }
1360 Ok(())
1361 }
1362
1363 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1365 self.resident.get_refs_from(id)
1366 }
1367
1368 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1370 self.resident.get_refs_to(id)
1371 }
1372
1373 pub fn expand_refs(
1375 &self,
1376 id: EntityId,
1377 depth: u32,
1378 ref_types: Option<&[RefType]>,
1379 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1380 let mut results = Vec::new();
1381 let mut visited = std::collections::HashSet::new();
1382 visited.insert(id);
1383
1384 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1385
1386 results
1387 }
1388
1389 fn expand_refs_recursive(
1390 &self,
1391 id: EntityId,
1392 max_depth: u32,
1393 ref_types: Option<&[RefType]>,
1394 visited: &mut std::collections::HashSet<EntityId>,
1395 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1396 current_depth: u32,
1397 ) {
1398 if current_depth > max_depth {
1399 return;
1400 }
1401
1402 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1403 if visited.contains(&target_id) {
1404 continue;
1405 }
1406
1407 if let Some(types) = ref_types {
1408 if !types.contains(&ref_type) {
1409 continue;
1410 }
1411 }
1412
1413 visited.insert(target_id);
1414
1415 if let Some(entity) = self.get(&target_collection, target_id) {
1416 results.push((entity, current_depth, ref_type));
1417
1418 self.expand_refs_recursive(
1420 target_id,
1421 max_depth,
1422 ref_types,
1423 visited,
1424 results,
1425 current_depth + 1,
1426 );
1427 }
1428 }
1429 }
1430
1431 pub(crate) fn index_cross_refs(
1433 &self,
1434 entity: &UnifiedEntity,
1435 collection: &str,
1436 ) -> Result<(), StoreError> {
1437 self.resident.index_cross_refs(entity, collection)
1438 }
1439
1440 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1442 self.resident.unindex_cross_refs(id)
1443 }
1444
1445 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1446 self.resident.unindex_cross_refs_batch(ids)
1447 }
1448
1449 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1453 self.resident.unindex_cross_refs_fast_path_hits()
1454 }
1455
1456 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1458 where
1459 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1460 {
1461 self.resident.query_all(filter)
1462 }
1463
1464 pub fn scan<F>(
1466 &self,
1467 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1468 filter: F,
1469 ) -> Vec<(String, UnifiedEntity)>
1470 where
1471 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1472 {
1473 self.resident.scan(snapshot, filter)
1474 }
1475
1476 pub fn filter_metadata_all(
1478 &self,
1479 filters: &[(String, MetadataFilter)],
1480 ) -> Vec<(String, EntityId)> {
1481 self.resident.filter_metadata_all(filters)
1482 }
1483
1484 pub fn stats(&self) -> StoreStats {
1486 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1492 let collections = self.resident.collections.read();
1493 collections
1494 .iter()
1495 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1496 .collect()
1497 };
1498
1499 let mut stats = StoreStats {
1500 collection_count: pairs.len(),
1501 ..Default::default()
1502 };
1503
1504 for (name, manager) in &pairs {
1505 let manager_stats = manager.stats();
1506 stats.total_entities += manager_stats.total_entities;
1507 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1508 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1509 stats.collections.insert(name.clone(), manager_stats);
1510 }
1511
1512 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1513 stats.total_memory_bytes += self
1514 .checkpoint_versions
1515 .read()
1516 .values()
1517 .filter_map(|version| version.deferred_rows.as_ref())
1518 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1519 .sum::<usize>();
1520 stats
1521 }
1522
1523 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1525 let collections = self.resident.collections.read();
1526 for manager in collections.values() {
1527 manager.run_maintenance()?;
1528 }
1529 Ok(())
1530 }
1531
1532 pub fn reclaimable_segment_bytes(&self) -> u64 {
1534 let collections = self.resident.collections.read();
1535 collections
1536 .values()
1537 .map(|manager| manager.reclaimable_bytes())
1538 .fold(0, u64::saturating_add)
1539 }
1540
1541 pub fn pressure_consolidation_tick(&self) -> bool {
1544 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1545 let mut advanced = false;
1548 for manager in &managers {
1549 advanced |= manager.pressure_consolidation_tick();
1550 }
1551 advanced
1552 }
1553}
1554
1555fn flatten_config_json(
1557 prefix: &str,
1558 value: &crate::serde_json::Value,
1559 out: &mut Vec<(String, reddb_types::Value)>,
1560) {
1561 use reddb_types::Value;
1562 match value {
1563 crate::serde_json::Value::Object(map) => {
1564 for (k, v) in map {
1565 let key = if prefix.is_empty() {
1566 k.clone()
1567 } else {
1568 format!("{prefix}.{k}")
1569 };
1570 flatten_config_json(&key, v, out);
1571 }
1572 }
1573 crate::serde_json::Value::String(s) => {
1574 out.push((prefix.to_string(), Value::text(s.clone())));
1575 }
1576 crate::serde_json::Value::Integer(n) => {
1577 out.push((prefix.to_string(), Value::Integer(*n)));
1578 }
1579 crate::serde_json::Value::Number(n) => {
1580 if n.fract().abs() < f64::EPSILON {
1581 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1582 } else {
1583 out.push((prefix.to_string(), Value::Float(*n)));
1584 }
1585 }
1586 crate::serde_json::Value::Decimal(n) => {
1587 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1588 }
1589 crate::serde_json::Value::Bool(b) => {
1590 out.push((prefix.to_string(), Value::Boolean(*b)));
1591 }
1592 crate::serde_json::Value::Null => {
1593 out.push((prefix.to_string(), Value::Null));
1594 }
1595 crate::serde_json::Value::Array(arr) => {
1596 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1597 out.push((prefix.to_string(), Value::text(json_str)));
1598 }
1599 }
1600}