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(crate) fn persist_aux_metadata(&self, bytes: Vec<u8>) -> Result<(), StoreError> {
432 let _publication = self.vault_publication_lock.lock();
433 self.finish_paged_write([StoreWalAction::SetAuxMetadata {
434 bytes: bytes.clone(),
435 }])?;
436 self.resident.set_aux_metadata(bytes);
437 Ok(())
438 }
439
440 pub fn aux_metadata(&self) -> Vec<u8> {
442 self.resident.aux_metadata()
443 }
444
445 pub fn list_collections(&self) -> Vec<String> {
447 self.resident.list_collections()
448 }
449
450 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
452 let _vault_guard = self.vault_publication_lock.lock();
453 let drop = self.prepare_operational_collection_drop(name)?;
454 let manager = {
455 let mut collections = self.resident.collections.write();
456
457 collections
458 .remove(name)
459 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
460 };
461
462 let entities = manager.query_all(|_| true);
463 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
464
465 for entity_id in &entity_ids {
466 self.resident.context_index.remove_entity(*entity_id);
467 let _ = self.unindex_cross_refs(*entity_id);
468 }
469
470 if let Some(tree) = self.btree_indices.write().remove(name) {
471 self.retire_btree(tree);
472 }
473
474 self.resident
475 .entity_cache
476 .retain(|entity_id, (collection, _)| {
477 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
478 });
479
480 self.resident.cross_refs.write().retain(|source_id, refs| {
481 refs.retain(|(target_id, _, target_collection)| {
482 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
483 });
484 !entity_ids.iter().any(|id| id == source_id)
485 });
486
487 self.resident
488 .reverse_refs
489 .write()
490 .retain(|target_id, refs| {
491 refs.retain(|(source_id, _, source_collection)| {
492 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
493 });
494 !entity_ids.iter().any(|id| id == target_id)
495 });
496
497 self.mark_paged_registry_dirty();
498 self.finish_paged_write([StoreWalAction::DropCollection {
499 name: name.to_string(),
500 }])?;
501 if let Some(drop) = drop {
502 drop.finish().map_err(StoreError::Io)?;
503 }
504
505 Ok(())
506 }
507
508 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
510 let manager = self
511 .get_collection(collection)
512 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
513
514 let mut entity = entity;
515 if entity.id.raw() == 0 {
516 entity.id = self.next_entity_id();
517 } else {
518 self.register_entity_id(entity.id);
519 }
520 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
522 if *row_id == 0 {
523 *row_id = manager.next_row_id();
524 } else {
525 manager.register_row_id(*row_id);
526 }
527 }
528 entity.ensure_table_logical_id();
529 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
531 {
532 Some(node.label.clone())
533 } else {
534 None
535 };
536
537 let id = manager.insert(entity)?;
538 self.register_entity_id(id);
539
540 if let Some(ref label) = graph_node_label {
542 self.update_graph_label_index(collection, label, id);
543 }
544
545 let mut registry_dirty = false;
548 if let Some(pager) = self
549 .pager
550 .as_ref()
551 .filter(|_| !Self::deferred_store_wal_capture_active())
552 {
553 if let Some(entity) = manager.get(id) {
554 let mut btree_indices = self.btree_indices.write();
555 let btree = btree_indices
556 .entry(collection.to_string())
557 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
558 let root_before = btree.root_page_id();
559
560 let key = id.raw().to_be_bytes();
561 let metadata = manager.get_metadata(id);
562 let value = Self::serialize_entity_record(
563 &entity,
564 metadata.as_ref(),
565 self.format_version(),
566 );
567 let _ = btree.insert(&key, &value);
569 registry_dirty = root_before != btree.root_page_id();
570 }
571 }
572
573 if self.config.auto_index_refs {
575 if let Some(entity) = manager.get(id) {
576 self.index_cross_refs(&entity, collection)?;
577 }
578 }
579
580 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
584 let actions = manager
585 .get(id)
586 .map(|entity| {
587 let metadata = manager.get_metadata(id);
588 vec![StoreWalAction::upsert_entity(
589 collection,
590 &entity,
591 metadata.as_ref(),
592 self.format_version(),
593 )]
594 })
595 .unwrap_or_default();
596 if registry_dirty {
597 self.mark_paged_registry_dirty();
598 }
599 self.finish_paged_write(actions)?;
600 }
601
602 Ok(id)
603 }
604
605 pub fn bulk_insert(
610 &self,
611 collection: &str,
612 mut entities: Vec<UnifiedEntity>,
613 ) -> Result<Vec<EntityId>, StoreError> {
614 let _publication = self.vault_publication_lock.lock();
617 let trace = matches!(
621 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
622 Some("1") | Some("true") | Some("on")
623 );
624 let t_start = std::time::Instant::now();
625 let n = entities.len();
626 let manager = self.get_or_create_collection(collection);
627 let t_get_coll = t_start.elapsed();
628
629 let t0 = std::time::Instant::now();
638 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
639 let n_missing_row_ids = entities
640 .iter()
641 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
642 .count() as u64;
643 let mut entity_id_range = if n_missing_entity_ids > 0 {
644 self.reserve_entity_ids(n_missing_entity_ids)
645 } else {
646 0..0
647 };
648 let mut row_id_range = if n_missing_row_ids > 0 {
649 manager.reserve_row_ids(n_missing_row_ids)
650 } else {
651 0..0
652 };
653 for entity in &mut entities {
654 if entity.id.raw() == 0 {
655 let next = entity_id_range
656 .next()
657 .expect("reserved entity-id range exhausted");
658 entity.id = EntityId::new(next);
659 } else {
660 self.register_entity_id(entity.id);
661 }
662 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
663 if *row_id == 0 {
664 *row_id = row_id_range
665 .next()
666 .expect("reserved row-id range exhausted");
667 } else {
668 manager.register_row_id(*row_id);
669 }
670 }
671 entity.ensure_table_logical_id();
672 }
673 let t_assign_ids = t0.elapsed();
674
675 let graph_labels: Vec<Option<(String, EntityId)>> = entities
677 .iter()
678 .map(|e| {
679 if let EntityKind::GraphNode(ref node) = e.kind {
680 Some((node.label.clone(), e.id))
681 } else {
682 None
683 }
684 })
685 .collect();
686
687 let t0 = std::time::Instant::now();
690 let ids = manager.bulk_insert(entities)?;
691 let t_manager = t0.elapsed();
692 for id in &ids {
693 self.register_entity_id(*id);
694 }
695
696 let t0 = std::time::Instant::now();
703 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
704 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
705 let fv = self.format_version();
706 let serial_map = |id: &EntityId| {
707 manager
708 .get_with(*id, |entity| {
709 (
710 id.raw().to_be_bytes().to_vec(),
711 Self::serialize_entity_record(entity, None, fv),
712 )
713 })
714 .expect("bulk-inserted entity must remain in the fenced segment")
715 };
716 if ids.len() >= 512 {
722 use rayon::prelude::*;
723 Some(ids.par_iter().map(serial_map).collect())
724 } else {
725 Some(ids.iter().map(serial_map).collect())
726 }
727 } else {
728 None
729 };
730 let t_serialize = t0.elapsed();
731
732 for (label, entity_id) in graph_labels.iter().flatten() {
734 self.update_graph_label_index(collection, label, *entity_id);
735 }
736
737 let skip_btree_requested = matches!(
747 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
748 .ok()
749 .as_deref(),
750 Some("1") | Some("true") | Some("on")
751 );
752 let skip_btree = skip_btree_requested && self.pager.is_none();
755 if skip_btree_requested && !skip_btree {
756 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
758 IGNORED.get_or_init(|| {
759 tracing::warn!(
760 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
761 active — flag ignored; bulk inserts will be persisted normally"
762 );
763 });
764 } else if skip_btree {
765 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
767 WARNED.get_or_init(|| {
768 tracing::warn!(
769 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
770 bulk inserts NOT durable; data will be lost on restart"
771 );
772 });
773 }
774
775 let mut t_btree_lock = std::time::Duration::ZERO;
779 let mut t_btree_insert = std::time::Duration::ZERO;
780 let mut t_flush = std::time::Duration::ZERO;
781 if !skip_btree && !Self::deferred_store_wal_capture_active() {
782 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
783 let t0 = std::time::Instant::now();
784 let mut btree_indices = self.btree_indices.write();
785 let btree = btree_indices
786 .entry(collection.to_string())
787 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
788 let root_before = btree.root_page_id();
789 t_btree_lock = t0.elapsed();
790
791 let t0 = std::time::Instant::now();
792 let _ = btree.bulk_insert_sorted(batch);
793 t_btree_insert = t0.elapsed();
794 let registry_dirty = root_before != btree.root_page_id();
795
796 let t0 = std::time::Instant::now();
797 if registry_dirty {
798 self.mark_paged_registry_dirty();
799 }
800 t_flush = t0.elapsed();
801 }
802 }
803
804 let actions = serialized
810 .map(|batch| {
811 let records: Vec<Vec<u8>> =
812 batch.into_iter().map(|(_key, record)| record).collect();
813 vec![StoreWalAction::BulkUpsertEntityRecords {
814 collection: collection.to_string(),
815 records,
816 }]
817 })
818 .unwrap_or_default();
819 self.finish_paged_write(actions)?;
820
821 if trace {
822 tracing::debug!(
823 n,
824 total = ?t_start.elapsed(),
825 get_coll = ?t_get_coll,
826 assign = ?t_assign_ids,
827 serialize = ?t_serialize,
828 manager = ?t_manager,
829 btree_lock = ?t_btree_lock,
830 btree = ?t_btree_insert,
831 flush = ?t_flush,
832 "bulk_insert timing"
833 );
834 }
835
836 Ok(ids)
837 }
838
839 pub fn insert_auto(
841 &self,
842 collection: &str,
843 entity: UnifiedEntity,
844 ) -> Result<EntityId, StoreError> {
845 let _publication = self.vault_publication_lock.lock();
847 let manager = self.get_or_create_collection(collection);
848 let mut entity = entity;
849 if entity.id.raw() == 0 {
850 entity.id = self.next_entity_id();
851 } else {
852 self.register_entity_id(entity.id);
853 }
854 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
856 if *row_id == 0 {
857 *row_id = manager.next_row_id();
858 } else {
859 manager.register_row_id(*row_id);
860 }
861 }
862 entity.ensure_table_logical_id();
863
864 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
866 {
867 Some(node.label.clone())
868 } else {
869 None
870 };
871 self.resident
873 .context_index
874 .index_entity(collection, &entity);
875
876 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
879 if self.config.auto_index_refs {
880 self.index_cross_refs(&entity, collection)?;
881 }
882
883 let paged_btree = if persist_record && !Self::deferred_store_wal_capture_active() {
890 self.get_or_create_btree(collection)
891 } else {
892 None
893 };
894 let persist_fence = paged_btree
895 .as_ref()
896 .map(|_| self.btree_indices.read_recursive());
897 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
898 let unversioned = entity.xmin == 0 && entity.xmax == 0;
899
900 let id = manager.insert(entity)?;
901 let serialized_record = if persist_record {
902 Some(
903 manager
904 .get_with(id, |entity| {
905 Self::serialize_entity_record(entity, None, self.format_version())
906 })
907 .ok_or(StoreError::EntityNotFound(id))?,
908 )
909 } else {
910 None
911 };
912 if let Some(ref label) = graph_node_label {
920 self.update_graph_label_index(collection, label, id);
921 }
922
923 let mut registry_dirty = false;
924 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
925 let root_before = btree.root_page_id();
926
927 let key = id.raw().to_be_bytes();
928 btree.insert(&key, record).map_err(|e| {
929 StoreError::Io(std::io::Error::other(format!(
930 "B-tree insert error while inserting '{collection}'/{id}: {e}"
931 )))
932 })?;
933 registry_dirty = root_before != btree.root_page_id();
934 }
935 drop(persist_fence);
936
937 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
941 let actions = serialized_record
942 .map(|record| {
943 vec![StoreWalAction::UpsertEntityRecord {
944 collection: collection.to_string(),
945 record,
946 }]
947 })
948 .unwrap_or_default();
949 if registry_dirty {
950 self.mark_paged_registry_dirty();
951 }
952 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
953 drop(_publication);
954 self.complete_grouped_actions(&pending, &actions)?;
955 } else {
956 self.finish_paged_write(actions)?;
957 }
958 }
959 if unversioned && !Self::deferred_store_wal_capture_active() {
960 if let Some(before) = before {
961 self.note_paged_insert(collection, &manager, &before, id);
962 }
963 }
964 Ok(id)
965 }
966
967 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
973 if let Some(entity) = self
975 .get_collection(collection)
976 .and_then(|manager| manager.get(id))
977 {
978 return Some(entity);
979 }
980
981 if self.pager.is_some() {
983 let btree_indices = self.btree_indices.read();
984 if let Some(btree) = btree_indices.get(collection) {
985 let key = id.raw().to_be_bytes();
986 if let Ok(Some(value)) = btree.get(&key) {
987 if let Ok((entity, _)) =
988 Self::deserialize_entity_record(&value, self.format_version())
989 {
990 return Some(entity);
991 }
992 }
993 }
994 }
995
996 None
997 }
998
999 pub fn get_table_row_by_logical_id(
1005 &self,
1006 collection: &str,
1007 logical_id: EntityId,
1008 ) -> Option<UnifiedEntity> {
1009 if let Some(entity) = self.get(collection, logical_id) {
1010 if matches!(entity.kind, EntityKind::TableRow { .. })
1011 && entity.logical_id() == logical_id
1012 && entity.xmax == 0
1013 {
1014 return Some(entity);
1015 }
1016 }
1017
1018 let manager = self.get_collection(collection)?;
1019 let mut matches = manager.query_all(|entity| {
1020 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1021 });
1022 matches
1023 .iter()
1024 .find(|entity| entity.xmax == 0)
1025 .cloned()
1026 .or_else(|| matches.pop())
1027 }
1028
1029 pub fn table_row_versions_by_logical_id(
1030 &self,
1031 collection: &str,
1032 logical_id: EntityId,
1033 ) -> Vec<UnifiedEntity> {
1034 self.resident
1035 .table_row_versions_by_logical_id(collection, logical_id)
1036 }
1037
1038 pub fn vacuum_mvcc_history(
1039 &self,
1040 collection: &str,
1041 cutoff_xid: u64,
1042 ) -> Result<MvccVacuumStats, StoreError> {
1043 let manager = self
1044 .get_collection(collection)
1045 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1046 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1047 manager.for_each_entity(|entity| {
1048 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1049 return true;
1050 }
1051 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1052 if entity.xmax == 0 {
1053 entry.0 = true;
1054 }
1055 entry.1 = entry.1.max(entity.xmin);
1056 true
1057 });
1058
1059 let mut stats = MvccVacuumStats::default();
1060 let mut reclaim_ids = Vec::new();
1061 manager.for_each_entity(|entity| {
1062 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1063 return true;
1064 }
1065 stats.scanned_versions += 1;
1066 let (has_live_version, max_xmin) = logical
1067 .get(&entity.logical_id())
1068 .copied()
1069 .unwrap_or((false, entity.xmin));
1070 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1071 if entity.xmax < cutoff_xid {
1072 stats.reclaimed_versions += 1;
1073 if is_delete_tombstone {
1074 stats.reclaimed_tombstones += 1;
1075 } else {
1076 stats.reclaimed_history_versions += 1;
1077 }
1078 reclaim_ids.push(entity.id);
1079 } else {
1080 stats.retained_versions += 1;
1081 if is_delete_tombstone {
1082 stats.retained_tombstones += 1;
1083 } else {
1084 stats.retained_history_versions += 1;
1085 }
1086 }
1087 true
1088 });
1089
1090 if !reclaim_ids.is_empty() {
1091 self.delete_batch(collection, &reclaim_ids)?;
1092 }
1093 Ok(stats)
1094 }
1095
1096 pub(crate) fn install_versioned_table_row_update(
1097 &self,
1098 collection: &str,
1099 old_version: UnifiedEntity,
1100 mut new_version: UnifiedEntity,
1101 metadata: Option<&Metadata>,
1102 ) -> Result<(), StoreError> {
1103 let manager = self
1104 .get_collection(collection)
1105 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1106
1107 let old_id = old_version.id;
1108 let new_id = new_version.id;
1109 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1110
1111 self.resident.entity_cache.remove(old_id.raw());
1112 self.resident.entity_cache.remove(new_id.raw());
1113 manager.update(old_version.clone())?;
1114 self.resident.context_index.remove_entity(old_id);
1115
1116 self.register_entity_id(new_version.id);
1117 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1118 if *row_id == 0 {
1119 *row_id = manager.next_row_id();
1120 } else {
1121 manager.register_row_id(*row_id);
1122 }
1123 }
1124 new_version.ensure_table_logical_id();
1125 manager.insert(new_version.clone())?;
1126 if let Some(metadata) = inherited_metadata {
1127 manager.set_metadata(new_id, metadata)?;
1128 }
1129 self.resident
1130 .context_index
1131 .index_entity(collection, &new_version);
1132 if self.config.auto_index_refs {
1133 self.index_cross_refs(&new_version, collection)?;
1134 }
1135
1136 let old_metadata = manager.get_metadata(old_id);
1137 let new_metadata = manager.get_metadata(new_id);
1138 let fv = self.format_version();
1139 let records = vec![
1142 manager
1143 .get_with(old_id, |entity| {
1144 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1145 })
1146 .ok_or(StoreError::EntityNotFound(old_id))?,
1147 manager
1148 .get_with(new_id, |entity| {
1149 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1150 })
1151 .ok_or(StoreError::EntityNotFound(new_id))?,
1152 ];
1153 self.finish_paged_write_for_projection(
1154 [StoreWalAction::BulkUpsertEntityRecords {
1155 collection: collection.to_string(),
1156 records,
1157 }],
1158 true,
1159 )?;
1160
1161 Ok(())
1162 }
1163
1164 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1170 match self.get_collection(collection) {
1171 Some(manager) => manager.get_many(ids),
1172 None => vec![None; ids.len()],
1173 }
1174 }
1175
1176 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1178 self.resident.get_any(id)
1179 }
1180
1181 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1187 self.resident.entity_cache_hit_rate()
1188 }
1189
1190 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1192 self.resident.entity_cache_stats()
1193 }
1194
1195 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1197 let publication = self.vault_publication_lock.lock();
1198 self.resident.entity_cache.remove(id.raw());
1200 let manager = self
1201 .get_collection(collection)
1202 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1203
1204 let deleted = manager.delete(id)?;
1205 if !deleted {
1206 return Ok(false);
1207 }
1208
1209 let mut registry_dirty = false;
1211 if self.pager.is_some() {
1212 let btree_indices = self.btree_indices.read();
1213 if let Some(btree) = btree_indices.get(collection) {
1214 let root_before = btree.root_page_id();
1215 let key = id.raw().to_be_bytes();
1216 let _ = btree.delete(&key);
1217 registry_dirty = root_before != btree.root_page_id();
1218 }
1219 }
1220
1221 self.unindex_cross_refs(id)?;
1223
1224 self.remove_from_graph_label_index(collection, id);
1226
1227 if registry_dirty {
1228 self.mark_paged_registry_dirty();
1229 }
1230 let actions = [StoreWalAction::DeleteEntityRecord {
1231 collection: collection.to_string(),
1232 entity_id: id.raw(),
1233 }];
1234 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1235 drop(publication);
1236 self.complete_grouped_actions(&pending, &actions)?;
1237 } else {
1238 self.finish_paged_write(actions)?;
1239 }
1240
1241 Ok(true)
1242 }
1243
1244 pub fn delete_batch(
1245 &self,
1246 collection: &str,
1247 ids: &[EntityId],
1248 ) -> Result<Vec<EntityId>, StoreError> {
1249 if ids.is_empty() {
1250 return Ok(Vec::new());
1251 }
1252 let _publication = self.vault_publication_lock.lock();
1255
1256 self.resident
1261 .entity_cache
1262 .remove_many(ids.iter().map(|id| id.raw()));
1263
1264 let manager = self
1265 .get_collection(collection)
1266 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1267
1268 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1269 let deleted_ids = manager.delete_batch(ids)?;
1270 if deleted_ids.is_empty() {
1271 return Ok(deleted_ids);
1272 }
1273
1274 let mut registry_dirty = false;
1275 let mut updated_tree = false;
1276 if self.pager.is_some() {
1277 let btree_indices = self.btree_indices.read();
1278 if let Some(btree) = btree_indices.get(collection) {
1279 updated_tree = true;
1280 let root_before = btree.root_page_id();
1281 for id in &deleted_ids {
1282 let key = id.raw().to_be_bytes();
1283 updated_tree &= btree.delete(&key).is_ok();
1286 }
1287 registry_dirty = root_before != btree.root_page_id();
1288 }
1289 }
1290
1291 self.unindex_cross_refs_batch(&deleted_ids)?;
1292 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1293 if registry_dirty {
1294 self.mark_paged_registry_dirty();
1295 }
1296 let actions = deleted_ids
1297 .iter()
1298 .map(|id| StoreWalAction::DeleteEntityRecord {
1299 collection: collection.to_string(),
1300 entity_id: id.raw(),
1301 })
1302 .collect::<Vec<_>>();
1303 self.finish_paged_write(actions)?;
1304
1305 if updated_tree {
1306 if let Some(before) = before {
1307 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1308 }
1309 self.flush_paged_registry()?;
1314 }
1315 Ok(deleted_ids)
1316 }
1317
1318 pub fn set_metadata(
1320 &self,
1321 collection: &str,
1322 id: EntityId,
1323 metadata: Metadata,
1324 ) -> Result<(), StoreError> {
1325 let manager = self
1326 .get_collection(collection)
1327 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1328
1329 manager.set_metadata(id, metadata)?;
1330 if let Some(entity) = manager.get(id) {
1331 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1332 }
1333 Ok(())
1334 }
1335
1336 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1338 self.get_collection(collection)?.get_metadata(id)
1339 }
1340
1341 pub fn add_cross_ref(
1343 &self,
1344 source_collection: &str,
1345 source_id: EntityId,
1346 target_collection: &str,
1347 target_id: EntityId,
1348 ref_type: RefType,
1349 weight: f32,
1350 ) -> Result<(), StoreError> {
1351 let registry_dirty = self.resident.add_cross_ref_in_memory(
1352 source_collection,
1353 source_id,
1354 target_collection,
1355 target_id,
1356 ref_type,
1357 weight,
1358 self.config.max_cross_refs,
1359 |entity| {
1360 self.persist_entities_to_pager(source_collection, std::slice::from_ref(entity))
1361 },
1362 )?;
1363 if registry_dirty
1364 && matches!(
1365 self.config.durability_mode,
1366 crate::api::DurabilityMode::Strict
1367 )
1368 {
1369 self.flush_paged_state()?;
1370 }
1371 Ok(())
1372 }
1373
1374 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1376 self.resident.get_refs_from(id)
1377 }
1378
1379 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1381 self.resident.get_refs_to(id)
1382 }
1383
1384 pub fn expand_refs(
1386 &self,
1387 id: EntityId,
1388 depth: u32,
1389 ref_types: Option<&[RefType]>,
1390 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1391 let mut results = Vec::new();
1392 let mut visited = std::collections::HashSet::new();
1393 visited.insert(id);
1394
1395 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1396
1397 results
1398 }
1399
1400 fn expand_refs_recursive(
1401 &self,
1402 id: EntityId,
1403 max_depth: u32,
1404 ref_types: Option<&[RefType]>,
1405 visited: &mut std::collections::HashSet<EntityId>,
1406 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1407 current_depth: u32,
1408 ) {
1409 if current_depth > max_depth {
1410 return;
1411 }
1412
1413 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1414 if visited.contains(&target_id) {
1415 continue;
1416 }
1417
1418 if let Some(types) = ref_types {
1419 if !types.contains(&ref_type) {
1420 continue;
1421 }
1422 }
1423
1424 visited.insert(target_id);
1425
1426 if let Some(entity) = self.get(&target_collection, target_id) {
1427 results.push((entity, current_depth, ref_type));
1428
1429 self.expand_refs_recursive(
1431 target_id,
1432 max_depth,
1433 ref_types,
1434 visited,
1435 results,
1436 current_depth + 1,
1437 );
1438 }
1439 }
1440 }
1441
1442 pub(crate) fn index_cross_refs(
1444 &self,
1445 entity: &UnifiedEntity,
1446 collection: &str,
1447 ) -> Result<(), StoreError> {
1448 self.resident.index_cross_refs(entity, collection)
1449 }
1450
1451 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1453 self.resident.unindex_cross_refs(id)
1454 }
1455
1456 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1457 self.resident.unindex_cross_refs_batch(ids)
1458 }
1459
1460 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1464 self.resident.unindex_cross_refs_fast_path_hits()
1465 }
1466
1467 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1469 where
1470 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1471 {
1472 self.resident.query_all(filter)
1473 }
1474
1475 pub fn scan<F>(
1477 &self,
1478 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1479 filter: F,
1480 ) -> Vec<(String, UnifiedEntity)>
1481 where
1482 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1483 {
1484 self.resident.scan(snapshot, filter)
1485 }
1486
1487 pub fn filter_metadata_all(
1489 &self,
1490 filters: &[(String, MetadataFilter)],
1491 ) -> Vec<(String, EntityId)> {
1492 self.resident.filter_metadata_all(filters)
1493 }
1494
1495 pub fn stats(&self) -> StoreStats {
1497 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1503 let collections = self.resident.collections.read();
1504 collections
1505 .iter()
1506 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1507 .collect()
1508 };
1509
1510 let mut stats = StoreStats {
1511 collection_count: pairs.len(),
1512 ..Default::default()
1513 };
1514
1515 for (name, manager) in &pairs {
1516 let manager_stats = manager.stats();
1517 stats.total_entities += manager_stats.total_entities;
1518 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1519 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1520 stats.collections.insert(name.clone(), manager_stats);
1521 }
1522
1523 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1524 stats.total_memory_bytes += self
1525 .checkpoint_versions
1526 .read()
1527 .values()
1528 .filter_map(|version| version.deferred_rows.as_ref())
1529 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1530 .sum::<usize>();
1531 stats
1532 }
1533
1534 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1536 let collections = self.resident.collections.read();
1537 for manager in collections.values() {
1538 manager.run_maintenance()?;
1539 }
1540 Ok(())
1541 }
1542
1543 pub fn reclaimable_segment_bytes(&self) -> u64 {
1545 let collections = self.resident.collections.read();
1546 collections
1547 .values()
1548 .map(|manager| manager.reclaimable_bytes())
1549 .fold(0, u64::saturating_add)
1550 }
1551
1552 pub fn pressure_consolidation_tick(&self) -> bool {
1555 let managers: Vec<_> = self.resident.collections.read().values().cloned().collect();
1556 let mut advanced = false;
1559 for manager in &managers {
1560 advanced |= manager.pressure_consolidation_tick();
1561 }
1562 advanced
1563 }
1564}
1565
1566fn flatten_config_json(
1568 prefix: &str,
1569 value: &crate::serde_json::Value,
1570 out: &mut Vec<(String, reddb_types::Value)>,
1571) {
1572 use reddb_types::Value;
1573 match value {
1574 crate::serde_json::Value::Object(map) => {
1575 for (k, v) in map {
1576 let key = if prefix.is_empty() {
1577 k.clone()
1578 } else {
1579 format!("{prefix}.{k}")
1580 };
1581 flatten_config_json(&key, v, out);
1582 }
1583 }
1584 crate::serde_json::Value::String(s) => {
1585 out.push((prefix.to_string(), Value::text(s.clone())));
1586 }
1587 crate::serde_json::Value::Integer(n) => {
1588 out.push((prefix.to_string(), Value::Integer(*n)));
1589 }
1590 crate::serde_json::Value::Number(n) => {
1591 if n.fract().abs() < f64::EPSILON {
1592 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1593 } else {
1594 out.push((prefix.to_string(), Value::Float(*n)));
1595 }
1596 }
1597 crate::serde_json::Value::Decimal(n) => {
1598 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1599 }
1600 crate::serde_json::Value::Bool(b) => {
1601 out.push((prefix.to_string(), Value::Boolean(*b)));
1602 }
1603 crate::serde_json::Value::Null => {
1604 out.push((prefix.to_string(), Value::Null));
1605 }
1606 crate::serde_json::Value::Array(arr) => {
1607 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1608 out.push((prefix.to_string(), Value::text(json_str)));
1609 }
1610 }
1611}