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 let key = (collection.to_string(), label.to_string());
228 let mut idx = self.graph_label_index.write();
229 idx.entry(key).or_default().push(entity_id);
230 }
231
232 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
234 let mut idx = self.graph_label_index.write();
235 for ((col, _), ids) in idx.iter_mut() {
236 if col == collection {
237 ids.retain(|&id| id != entity_id);
238 }
239 }
240 idx.retain(|_, ids| !ids.is_empty());
242 }
243
244 pub(crate) fn remove_from_graph_label_index_batch(
245 &self,
246 collection: &str,
247 entity_ids: &[EntityId],
248 ) {
249 if entity_ids.is_empty() {
250 return;
251 }
252 let id_set: std::collections::HashSet<EntityId> = entity_ids.iter().copied().collect();
253 let mut idx = self.graph_label_index.write();
254 for ((col, _), ids) in idx.iter_mut() {
255 if col == collection {
256 ids.retain(|id| !id_set.contains(id));
257 }
258 }
259 idx.retain(|_, ids| !ids.is_empty());
260 }
261
262 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
264 let idx = self.graph_label_index.read();
265 idx.iter()
266 .filter(|((_, l), _)| l == label)
267 .flat_map(|(_, ids)| ids.iter().copied())
268 .collect()
269 }
270
271 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
273 let idx = self.graph_label_index.read();
274 idx.get(&(collection.to_string(), label.to_string()))
275 .cloned()
276 .unwrap_or_default()
277 }
278
279 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
280 let name = name.into();
281 let mut collections = self.collections.write();
282
283 if collections.contains_key(&name) {
284 return Err(StoreError::CollectionExists(name));
285 }
286
287 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
288 collections.insert(name.clone(), Arc::new(manager));
289 drop(collections);
290
291 let create = match self.prepare_operational_collection_create(&name) {
292 Ok(create) => create,
293 Err(err) => {
294 let mut collections = self.collections.write();
295 collections.remove(&name);
296 return Err(err);
297 }
298 };
299 if let Some(create) = create {
300 if let Err(err) = create.publish().map_err(StoreError::Io) {
301 let mut collections = self.collections.write();
302 collections.remove(&name);
303 return Err(err);
304 }
305 }
306
307 self.mark_paged_registry_dirty();
308 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
309
310 Ok(())
311 }
312
313 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
315 let name = name.into();
316 {
318 let collections = self.collections.read();
319 if let Some(manager) = collections.get(&name) {
320 return Arc::clone(manager);
321 }
322 }
323 let mut collections = self.collections.write();
325 if let Some(manager) = collections.get(&name) {
327 return Arc::clone(manager);
328 }
329 let manager = Arc::new(SegmentManager::with_config(
330 &name,
331 self.config.manager_config.clone(),
332 ));
333 collections.insert(name, Arc::clone(&manager));
334 self.mark_paged_registry_dirty();
335 manager
336 }
337
338 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
340 self.collections.read().get(name).map(Arc::clone)
341 }
342
343 pub fn context_index(&self) -> &ContextIndex {
345 &self.context_index
346 }
347
348 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
354 let mut map = self.replayed_turbo_inserts.lock();
355 map.remove(collection)
356 }
357
358 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
359 let mut deltas = self.replayed_probabilistic_deltas.lock();
360 std::mem::take(&mut *deltas)
361 }
362
363 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
370 let manager = self.get_or_create_collection("red_config");
371 let mut pairs = Vec::new();
372 flatten_config_json(prefix, json, &mut pairs);
373 let mut previous: HashMap<String, Vec<(EntityId, Option<reddb_types::Value>)>> = pairs
375 .iter()
376 .map(|(key, _)| (key.clone(), Vec::new()))
377 .collect();
378 for entity in manager.query_all(|_| true) {
379 if let EntityData::Row(row) = &entity.data {
380 let Some(named) = row.named.as_ref() else {
381 continue;
382 };
383 if let Some(reddb_types::Value::Text(key)) = named.get("key") {
384 if let Some(rows) = previous.get_mut(key.as_ref()) {
385 rows.push((entity.id, named.get("value").cloned()));
386 }
387 }
388 }
389 }
390 let mut saved = 0;
391 for (key, value) in pairs {
392 let stale = previous.remove(&key).unwrap_or_default();
393 if let [(_, Some(current))] = stale.as_slice() {
397 if *current == value {
398 saved += 1;
399 continue;
400 }
401 }
402 let stale: Vec<EntityId> = stale.into_iter().map(|(id, _)| id).collect();
403 let entity = UnifiedEntity::new(
407 EntityId::new(0),
408 EntityKind::TableRow {
409 table: Arc::from("red_config"),
410 row_id: 0,
411 },
412 EntityData::Row(RowData {
413 columns: Vec::new(),
414 named: Some(
415 [
416 ("key".to_string(), reddb_types::Value::text(key)),
417 ("value".to_string(), value),
418 ]
419 .into_iter()
420 .collect(),
421 ),
422 schema: None,
423 }),
424 );
425 if self.insert_auto("red_config", entity).is_ok() {
426 saved += 1;
427 if !stale.is_empty() {
428 let _ = self.delete_batch("red_config", &stale);
429 }
430 }
431 }
432 saved
433 }
434
435 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
439 let manager = self.get_collection("red_config")?;
440 let mut latest: Option<(u64, reddb_types::Value)> = None;
441 for entity in manager.query_all(|_| true) {
442 if let EntityData::Row(row) = &entity.data {
443 if let Some(named) = &row.named {
444 let key_matches = named
445 .get("key")
446 .and_then(|v| match v {
447 reddb_types::Value::Text(s) => Some(s.as_ref() == key),
448 _ => None,
449 })
450 .unwrap_or(false);
451 if key_matches && latest.as_ref().is_none_or(|(id, _)| entity.id.raw() >= *id) {
452 if let Some(value) = named.get("value") {
453 latest = Some((entity.id.raw(), value.clone()));
454 }
455 }
456 }
457 }
458 }
459 latest.map(|(_, value)| value)
460 }
461
462 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
467 *self.aux_metadata.write() = bytes;
468 }
469
470 pub fn aux_metadata(&self) -> Vec<u8> {
472 self.aux_metadata.read().clone()
473 }
474
475 pub fn list_collections(&self) -> Vec<String> {
477 self.collections.read().keys().cloned().collect()
478 }
479
480 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
482 let _vault_guard = self.vault_publication_lock.lock();
483 let drop = self.prepare_operational_collection_drop(name)?;
484 let manager = {
485 let mut collections = self.collections.write();
486
487 collections
488 .remove(name)
489 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
490 };
491
492 let entities = manager.query_all(|_| true);
493 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
494
495 for entity_id in &entity_ids {
496 self.context_index.remove_entity(*entity_id);
497 let _ = self.unindex_cross_refs(*entity_id);
498 }
499
500 if let Some(tree) = self.btree_indices.write().remove(name) {
501 self.retire_btree(tree);
502 }
503
504 self.entity_cache.retain(|entity_id, (collection, _)| {
505 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
506 });
507
508 self.cross_refs.write().retain(|source_id, refs| {
509 refs.retain(|(target_id, _, target_collection)| {
510 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
511 });
512 !entity_ids.iter().any(|id| id == source_id)
513 });
514
515 self.reverse_refs.write().retain(|target_id, refs| {
516 refs.retain(|(source_id, _, source_collection)| {
517 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
518 });
519 !entity_ids.iter().any(|id| id == target_id)
520 });
521
522 self.mark_paged_registry_dirty();
523 self.finish_paged_write([StoreWalAction::DropCollection {
524 name: name.to_string(),
525 }])?;
526 if let Some(drop) = drop {
527 drop.finish().map_err(StoreError::Io)?;
528 }
529
530 Ok(())
531 }
532
533 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
535 let manager = self
536 .get_collection(collection)
537 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
538
539 let mut entity = entity;
540 if entity.id.raw() == 0 {
541 entity.id = self.next_entity_id();
542 } else {
543 self.register_entity_id(entity.id);
544 }
545 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
547 if *row_id == 0 {
548 *row_id = manager.next_row_id();
549 } else {
550 manager.register_row_id(*row_id);
551 }
552 }
553 entity.ensure_table_logical_id();
554 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
556 {
557 Some(node.label.clone())
558 } else {
559 None
560 };
561
562 let id = manager.insert(entity)?;
563 self.register_entity_id(id);
564
565 if let Some(ref label) = graph_node_label {
567 self.update_graph_label_index(collection, label, id);
568 }
569
570 let mut registry_dirty = false;
573 if let Some(pager) = self
574 .pager
575 .as_ref()
576 .filter(|_| !Self::deferred_store_wal_capture_active())
577 {
578 if let Some(entity) = manager.get(id) {
579 let mut btree_indices = self.btree_indices.write();
580 let btree = btree_indices
581 .entry(collection.to_string())
582 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
583 let root_before = btree.root_page_id();
584
585 let key = id.raw().to_be_bytes();
586 let metadata = manager.get_metadata(id);
587 let value = Self::serialize_entity_record(
588 &entity,
589 metadata.as_ref(),
590 self.format_version(),
591 );
592 let _ = btree.insert(&key, &value);
594 registry_dirty = root_before != btree.root_page_id();
595 }
596 }
597
598 if self.config.auto_index_refs {
600 if let Some(entity) = manager.get(id) {
601 self.index_cross_refs(&entity, collection)?;
602 }
603 }
604
605 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
609 let actions = manager
610 .get(id)
611 .map(|entity| {
612 let metadata = manager.get_metadata(id);
613 vec![StoreWalAction::upsert_entity(
614 collection,
615 &entity,
616 metadata.as_ref(),
617 self.format_version(),
618 )]
619 })
620 .unwrap_or_default();
621 if registry_dirty {
622 self.mark_paged_registry_dirty();
623 }
624 self.finish_paged_write(actions)?;
625 }
626
627 Ok(id)
628 }
629
630 pub fn bulk_insert(
635 &self,
636 collection: &str,
637 mut entities: Vec<UnifiedEntity>,
638 ) -> Result<Vec<EntityId>, StoreError> {
639 let _publication = self.vault_publication_lock.lock();
642 let trace = matches!(
646 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
647 Some("1") | Some("true") | Some("on")
648 );
649 let t_start = std::time::Instant::now();
650 let n = entities.len();
651 let manager = self.get_or_create_collection(collection);
652 let t_get_coll = t_start.elapsed();
653
654 let t0 = std::time::Instant::now();
663 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
664 let n_missing_row_ids = entities
665 .iter()
666 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
667 .count() as u64;
668 let mut entity_id_range = if n_missing_entity_ids > 0 {
669 self.reserve_entity_ids(n_missing_entity_ids)
670 } else {
671 0..0
672 };
673 let mut row_id_range = if n_missing_row_ids > 0 {
674 manager.reserve_row_ids(n_missing_row_ids)
675 } else {
676 0..0
677 };
678 for entity in &mut entities {
679 if entity.id.raw() == 0 {
680 let next = entity_id_range
681 .next()
682 .expect("reserved entity-id range exhausted");
683 entity.id = EntityId::new(next);
684 } else {
685 self.register_entity_id(entity.id);
686 }
687 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
688 if *row_id == 0 {
689 *row_id = row_id_range
690 .next()
691 .expect("reserved row-id range exhausted");
692 } else {
693 manager.register_row_id(*row_id);
694 }
695 }
696 entity.ensure_table_logical_id();
697 }
698 let t_assign_ids = t0.elapsed();
699
700 let graph_labels: Vec<Option<(String, EntityId)>> = entities
702 .iter()
703 .map(|e| {
704 if let EntityKind::GraphNode(ref node) = e.kind {
705 Some((node.label.clone(), e.id))
706 } else {
707 None
708 }
709 })
710 .collect();
711
712 let t0 = std::time::Instant::now();
715 let ids = manager.bulk_insert(entities)?;
716 let t_manager = t0.elapsed();
717 for id in &ids {
718 self.register_entity_id(*id);
719 }
720
721 let t0 = std::time::Instant::now();
728 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
729 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
730 let fv = self.format_version();
731 let serial_map = |id: &EntityId| {
732 manager
733 .get_with(*id, |entity| {
734 (
735 id.raw().to_be_bytes().to_vec(),
736 Self::serialize_entity_record(entity, None, fv),
737 )
738 })
739 .expect("bulk-inserted entity must remain in the fenced segment")
740 };
741 if ids.len() >= 512 {
747 use rayon::prelude::*;
748 Some(ids.par_iter().map(serial_map).collect())
749 } else {
750 Some(ids.iter().map(serial_map).collect())
751 }
752 } else {
753 None
754 };
755 let t_serialize = t0.elapsed();
756
757 for (label, entity_id) in graph_labels.iter().flatten() {
759 self.update_graph_label_index(collection, label, *entity_id);
760 }
761
762 let skip_btree_requested = matches!(
772 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
773 .ok()
774 .as_deref(),
775 Some("1") | Some("true") | Some("on")
776 );
777 let skip_btree = skip_btree_requested && self.pager.is_none();
780 if skip_btree_requested && !skip_btree {
781 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
783 IGNORED.get_or_init(|| {
784 tracing::warn!(
785 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
786 active — flag ignored; bulk inserts will be persisted normally"
787 );
788 });
789 } else if skip_btree {
790 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
792 WARNED.get_or_init(|| {
793 tracing::warn!(
794 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
795 bulk inserts NOT durable; data will be lost on restart"
796 );
797 });
798 }
799
800 let mut t_btree_lock = std::time::Duration::ZERO;
804 let mut t_btree_insert = std::time::Duration::ZERO;
805 let mut t_flush = std::time::Duration::ZERO;
806 if !skip_btree && !Self::deferred_store_wal_capture_active() {
807 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
808 let t0 = std::time::Instant::now();
809 let mut btree_indices = self.btree_indices.write();
810 let btree = btree_indices
811 .entry(collection.to_string())
812 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
813 let root_before = btree.root_page_id();
814 t_btree_lock = t0.elapsed();
815
816 let t0 = std::time::Instant::now();
817 let _ = btree.bulk_insert_sorted(batch);
818 t_btree_insert = t0.elapsed();
819 let registry_dirty = root_before != btree.root_page_id();
820
821 let t0 = std::time::Instant::now();
822 if registry_dirty {
823 self.mark_paged_registry_dirty();
824 }
825 t_flush = t0.elapsed();
826 }
827 }
828
829 let actions = serialized
835 .map(|batch| {
836 let records: Vec<Vec<u8>> =
837 batch.into_iter().map(|(_key, record)| record).collect();
838 vec![StoreWalAction::BulkUpsertEntityRecords {
839 collection: collection.to_string(),
840 records,
841 }]
842 })
843 .unwrap_or_default();
844 self.finish_paged_write(actions)?;
845
846 if trace {
847 tracing::debug!(
848 n,
849 total = ?t_start.elapsed(),
850 get_coll = ?t_get_coll,
851 assign = ?t_assign_ids,
852 serialize = ?t_serialize,
853 manager = ?t_manager,
854 btree_lock = ?t_btree_lock,
855 btree = ?t_btree_insert,
856 flush = ?t_flush,
857 "bulk_insert timing"
858 );
859 }
860
861 Ok(ids)
862 }
863
864 pub fn insert_auto(
866 &self,
867 collection: &str,
868 entity: UnifiedEntity,
869 ) -> Result<EntityId, StoreError> {
870 let _publication = self.vault_publication_lock.lock();
872 let manager = self.get_or_create_collection(collection);
873 let mut entity = entity;
874 if entity.id.raw() == 0 {
875 entity.id = self.next_entity_id();
876 } else {
877 self.register_entity_id(entity.id);
878 }
879 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
881 if *row_id == 0 {
882 *row_id = manager.next_row_id();
883 } else {
884 manager.register_row_id(*row_id);
885 }
886 }
887 entity.ensure_table_logical_id();
888
889 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
891 {
892 Some(node.label.clone())
893 } else {
894 None
895 };
896 self.context_index.index_entity(collection, &entity);
898
899 let persist_record = self.pager.is_some() || self.config.embedded_wal_path.is_some();
902 if self.config.auto_index_refs {
903 self.index_cross_refs(&entity, collection)?;
904 }
905
906 let paged_btree = if persist_record && !Self::deferred_store_wal_capture_active() {
913 self.get_or_create_btree(collection)
914 } else {
915 None
916 };
917 let persist_fence = paged_btree
918 .as_ref()
919 .map(|_| self.btree_indices.read_recursive());
920 let before = paged_btree.as_ref().map(|_| manager.checkpoint_revisions());
921 let unversioned = entity.xmin == 0 && entity.xmax == 0;
922
923 let id = manager.insert(entity)?;
924 let serialized_record = if persist_record {
925 Some(
926 manager
927 .get_with(id, |entity| {
928 Self::serialize_entity_record(entity, None, self.format_version())
929 })
930 .ok_or(StoreError::EntityNotFound(id))?,
931 )
932 } else {
933 None
934 };
935 if let Some(ref label) = graph_node_label {
943 self.update_graph_label_index(collection, label, id);
944 }
945
946 let mut registry_dirty = false;
947 if let (Some(btree), Some(record)) = (paged_btree.as_ref(), serialized_record.as_ref()) {
948 let root_before = btree.root_page_id();
949
950 let key = id.raw().to_be_bytes();
951 btree.insert(&key, record).map_err(|e| {
952 StoreError::Io(std::io::Error::other(format!(
953 "B-tree insert error while inserting '{collection}'/{id}: {e}"
954 )))
955 })?;
956 registry_dirty = root_before != btree.root_page_id();
957 }
958 drop(persist_fence);
959
960 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
964 let actions = serialized_record
965 .map(|record| {
966 vec![StoreWalAction::UpsertEntityRecord {
967 collection: collection.to_string(),
968 record,
969 }]
970 })
971 .unwrap_or_default();
972 if registry_dirty {
973 self.mark_paged_registry_dirty();
974 }
975 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
976 drop(_publication);
977 self.complete_grouped_actions(&pending, &actions)?;
978 } else {
979 self.finish_paged_write(actions)?;
980 }
981 }
982 if unversioned && !Self::deferred_store_wal_capture_active() {
983 if let Some(before) = before {
984 self.note_paged_insert(collection, &manager, &before, id);
985 }
986 }
987 Ok(id)
988 }
989
990 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
996 if let Some(entity) = self
998 .get_collection(collection)
999 .and_then(|manager| manager.get(id))
1000 {
1001 return Some(entity);
1002 }
1003
1004 if self.pager.is_some() {
1006 let btree_indices = self.btree_indices.read();
1007 if let Some(btree) = btree_indices.get(collection) {
1008 let key = id.raw().to_be_bytes();
1009 if let Ok(Some(value)) = btree.get(&key) {
1010 if let Ok((entity, _)) =
1011 Self::deserialize_entity_record(&value, self.format_version())
1012 {
1013 return Some(entity);
1014 }
1015 }
1016 }
1017 }
1018
1019 None
1020 }
1021
1022 pub fn get_table_row_by_logical_id(
1028 &self,
1029 collection: &str,
1030 logical_id: EntityId,
1031 ) -> Option<UnifiedEntity> {
1032 if let Some(entity) = self.get(collection, logical_id) {
1033 if matches!(entity.kind, EntityKind::TableRow { .. })
1034 && entity.logical_id() == logical_id
1035 && entity.xmax == 0
1036 {
1037 return Some(entity);
1038 }
1039 }
1040
1041 let manager = self.get_collection(collection)?;
1042 let mut matches = manager.query_all(|entity| {
1043 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
1044 });
1045 matches
1046 .iter()
1047 .find(|entity| entity.xmax == 0)
1048 .cloned()
1049 .or_else(|| matches.pop())
1050 }
1051
1052 pub fn table_row_versions_by_logical_id(
1053 &self,
1054 collection: &str,
1055 logical_id: EntityId,
1056 ) -> Vec<UnifiedEntity> {
1057 self.get_collection(collection)
1058 .map(|manager| {
1059 manager.query_all(|entity| {
1060 matches!(entity.kind, EntityKind::TableRow { .. })
1061 && entity.logical_id() == logical_id
1062 })
1063 })
1064 .unwrap_or_default()
1065 }
1066
1067 pub fn vacuum_mvcc_history(
1068 &self,
1069 collection: &str,
1070 cutoff_xid: u64,
1071 ) -> Result<MvccVacuumStats, StoreError> {
1072 let manager = self
1073 .get_collection(collection)
1074 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1075 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
1076 manager.for_each_entity(|entity| {
1077 if !matches!(entity.kind, EntityKind::TableRow { .. }) {
1078 return true;
1079 }
1080 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
1081 if entity.xmax == 0 {
1082 entry.0 = true;
1083 }
1084 entry.1 = entry.1.max(entity.xmin);
1085 true
1086 });
1087
1088 let mut stats = MvccVacuumStats::default();
1089 let mut reclaim_ids = Vec::new();
1090 manager.for_each_entity(|entity| {
1091 if !matches!(entity.kind, EntityKind::TableRow { .. }) || entity.xmax == 0 {
1092 return true;
1093 }
1094 stats.scanned_versions += 1;
1095 let (has_live_version, max_xmin) = logical
1096 .get(&entity.logical_id())
1097 .copied()
1098 .unwrap_or((false, entity.xmin));
1099 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
1100 if entity.xmax < cutoff_xid {
1101 stats.reclaimed_versions += 1;
1102 if is_delete_tombstone {
1103 stats.reclaimed_tombstones += 1;
1104 } else {
1105 stats.reclaimed_history_versions += 1;
1106 }
1107 reclaim_ids.push(entity.id);
1108 } else {
1109 stats.retained_versions += 1;
1110 if is_delete_tombstone {
1111 stats.retained_tombstones += 1;
1112 } else {
1113 stats.retained_history_versions += 1;
1114 }
1115 }
1116 true
1117 });
1118
1119 if !reclaim_ids.is_empty() {
1120 self.delete_batch(collection, &reclaim_ids)?;
1121 }
1122 Ok(stats)
1123 }
1124
1125 pub(crate) fn install_versioned_table_row_update(
1126 &self,
1127 collection: &str,
1128 old_version: UnifiedEntity,
1129 mut new_version: UnifiedEntity,
1130 metadata: Option<&Metadata>,
1131 ) -> Result<(), StoreError> {
1132 let manager = self
1133 .get_collection(collection)
1134 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1135
1136 let old_id = old_version.id;
1137 let new_id = new_version.id;
1138 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1139
1140 self.entity_cache.remove(old_id.raw());
1141 self.entity_cache.remove(new_id.raw());
1142 manager.update(old_version.clone())?;
1143 self.context_index.remove_entity(old_id);
1144
1145 self.register_entity_id(new_version.id);
1146 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1147 if *row_id == 0 {
1148 *row_id = manager.next_row_id();
1149 } else {
1150 manager.register_row_id(*row_id);
1151 }
1152 }
1153 new_version.ensure_table_logical_id();
1154 manager.insert(new_version.clone())?;
1155 if let Some(metadata) = inherited_metadata {
1156 manager.set_metadata(new_id, metadata)?;
1157 }
1158 self.context_index.index_entity(collection, &new_version);
1159 if self.config.auto_index_refs {
1160 self.index_cross_refs(&new_version, collection)?;
1161 }
1162
1163 let old_metadata = manager.get_metadata(old_id);
1164 let new_metadata = manager.get_metadata(new_id);
1165 let fv = self.format_version();
1166 let records = vec![
1169 manager
1170 .get_with(old_id, |entity| {
1171 Self::serialize_entity_record(entity, old_metadata.as_ref(), fv)
1172 })
1173 .ok_or(StoreError::EntityNotFound(old_id))?,
1174 manager
1175 .get_with(new_id, |entity| {
1176 Self::serialize_entity_record(entity, new_metadata.as_ref(), fv)
1177 })
1178 .ok_or(StoreError::EntityNotFound(new_id))?,
1179 ];
1180 self.finish_paged_write_for_projection(
1181 [StoreWalAction::BulkUpsertEntityRecords {
1182 collection: collection.to_string(),
1183 records,
1184 }],
1185 true,
1186 )?;
1187
1188 Ok(())
1189 }
1190
1191 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1197 match self.get_collection(collection) {
1198 Some(manager) => manager.get_many(ids),
1199 None => vec![None; ids.len()],
1200 }
1201 }
1202
1203 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1205 if let Some(cached) = self.entity_cache.get(id.raw()) {
1209 return Some(cached);
1210 }
1211
1212 let collections = self.collections.read();
1214 for (name, manager) in collections.iter() {
1215 if let Some(entity) = manager.get(id) {
1216 let result = (name.clone(), entity);
1217 drop(collections);
1221 self.entity_cache.insert(id.raw(), result.clone());
1222 return Some(result);
1223 }
1224 }
1225 None
1226 }
1227
1228 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1234 self.entity_cache.hit_rate()
1235 }
1236
1237 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1239 self.entity_cache.stats()
1240 }
1241
1242 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1244 let publication = self.vault_publication_lock.lock();
1245 self.entity_cache.remove(id.raw());
1247 let manager = self
1248 .get_collection(collection)
1249 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1250
1251 let deleted = manager.delete(id)?;
1252 if !deleted {
1253 return Ok(false);
1254 }
1255
1256 let mut registry_dirty = false;
1258 if self.pager.is_some() {
1259 let btree_indices = self.btree_indices.read();
1260 if let Some(btree) = btree_indices.get(collection) {
1261 let root_before = btree.root_page_id();
1262 let key = id.raw().to_be_bytes();
1263 let _ = btree.delete(&key);
1264 registry_dirty = root_before != btree.root_page_id();
1265 }
1266 }
1267
1268 self.unindex_cross_refs(id)?;
1270
1271 self.remove_from_graph_label_index(collection, id);
1273
1274 if registry_dirty {
1275 self.mark_paged_registry_dirty();
1276 }
1277 let actions = [StoreWalAction::DeleteEntityRecord {
1278 collection: collection.to_string(),
1279 entity_id: id.raw(),
1280 }];
1281 if let Some(pending) = self.enqueue_grouped_actions(&actions)? {
1282 drop(publication);
1283 self.complete_grouped_actions(&pending, &actions)?;
1284 } else {
1285 self.finish_paged_write(actions)?;
1286 }
1287
1288 Ok(true)
1289 }
1290
1291 pub fn delete_batch(
1292 &self,
1293 collection: &str,
1294 ids: &[EntityId],
1295 ) -> Result<Vec<EntityId>, StoreError> {
1296 if ids.is_empty() {
1297 return Ok(Vec::new());
1298 }
1299 let _publication = self.vault_publication_lock.lock();
1302
1303 self.entity_cache.remove_many(ids.iter().map(|id| id.raw()));
1308
1309 let manager = self
1310 .get_collection(collection)
1311 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1312
1313 let before = self.pager.as_ref().map(|_| manager.checkpoint_revisions());
1314 let deleted_ids = manager.delete_batch(ids)?;
1315 if deleted_ids.is_empty() {
1316 return Ok(deleted_ids);
1317 }
1318
1319 let mut registry_dirty = false;
1320 let mut updated_tree = false;
1321 if self.pager.is_some() {
1322 let btree_indices = self.btree_indices.read();
1323 if let Some(btree) = btree_indices.get(collection) {
1324 updated_tree = true;
1325 let root_before = btree.root_page_id();
1326 for id in &deleted_ids {
1327 let key = id.raw().to_be_bytes();
1328 updated_tree &= btree.delete(&key).is_ok();
1331 }
1332 registry_dirty = root_before != btree.root_page_id();
1333 }
1334 }
1335
1336 self.unindex_cross_refs_batch(&deleted_ids)?;
1337 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1338 if registry_dirty {
1339 self.mark_paged_registry_dirty();
1340 }
1341 let actions = deleted_ids
1342 .iter()
1343 .map(|id| StoreWalAction::DeleteEntityRecord {
1344 collection: collection.to_string(),
1345 entity_id: id.raw(),
1346 })
1347 .collect::<Vec<_>>();
1348 self.finish_paged_write(actions)?;
1349
1350 if updated_tree {
1351 if let Some(before) = before {
1352 self.note_paged_delete(collection, &manager, &before, &deleted_ids);
1353 }
1354 self.flush_paged_registry()?;
1359 }
1360 Ok(deleted_ids)
1361 }
1362
1363 pub fn set_metadata(
1365 &self,
1366 collection: &str,
1367 id: EntityId,
1368 metadata: Metadata,
1369 ) -> Result<(), StoreError> {
1370 let manager = self
1371 .get_collection(collection)
1372 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1373
1374 manager.set_metadata(id, metadata)?;
1375 if let Some(entity) = manager.get(id) {
1376 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1377 }
1378 Ok(())
1379 }
1380
1381 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1383 self.get_collection(collection)?.get_metadata(id)
1384 }
1385
1386 pub fn add_cross_ref(
1388 &self,
1389 source_collection: &str,
1390 source_id: EntityId,
1391 target_collection: &str,
1392 target_id: EntityId,
1393 ref_type: RefType,
1394 weight: f32,
1395 ) -> Result<(), StoreError> {
1396 let source_manager = self
1398 .get_collection(source_collection)
1399 .ok_or_else(|| StoreError::CollectionNotFound(source_collection.to_string()))?;
1400
1401 if source_manager.get(source_id).is_none() {
1402 return Err(StoreError::EntityNotFound(source_id));
1403 }
1404
1405 let target_manager = self
1407 .get_collection(target_collection)
1408 .ok_or_else(|| StoreError::CollectionNotFound(target_collection.to_string()))?;
1409
1410 if target_manager.get(target_id).is_none() {
1411 return Err(StoreError::EntityNotFound(target_id));
1412 }
1413
1414 let current_refs = self
1416 .cross_refs
1417 .read()
1418 .get(&source_id)
1419 .map_or(0, |v| v.len());
1420
1421 if current_refs >= self.config.max_cross_refs {
1422 return Err(StoreError::TooManyRefs(source_id));
1423 }
1424
1425 let mut registry_dirty = false;
1426 {
1427 let mut forward = self.cross_refs.write();
1428 let refs = forward.entry(source_id).or_default();
1429 let inserted = !refs.iter().any(|(id, kind, coll)| {
1430 *id == target_id && *kind == ref_type && coll == target_collection
1431 });
1432 if inserted {
1433 refs.push((target_id, ref_type, target_collection.to_string()));
1434 registry_dirty = true;
1435 }
1436 }
1437
1438 {
1439 let mut reverse = self.reverse_refs.write();
1440 let refs = reverse.entry(target_id).or_default();
1441 let inserted = !refs.iter().any(|(id, kind, coll)| {
1442 *id == source_id && *kind == ref_type && coll == source_collection
1443 });
1444 if inserted {
1445 refs.push((source_id, ref_type, source_collection.to_string()));
1446 registry_dirty = true;
1447 }
1448 }
1449
1450 if let Some(mut entity) = source_manager.get(source_id) {
1451 if !entity.cross_refs().iter().any(|xref| {
1452 xref.target == target_id
1453 && xref.ref_type == ref_type
1454 && xref.target_collection == target_collection
1455 }) {
1456 let cross_ref = CrossRef::with_weight(
1457 source_id,
1458 target_id,
1459 target_collection,
1460 ref_type,
1461 weight,
1462 );
1463 entity.add_cross_ref(cross_ref);
1464 let _ = source_manager.update(entity.clone());
1465 registry_dirty = true;
1466 self.persist_entities_to_pager(source_collection, std::slice::from_ref(&entity))?;
1467 }
1468 }
1469
1470 if registry_dirty {
1471 self.mark_paged_registry_dirty();
1472 if matches!(
1473 self.config.durability_mode,
1474 crate::api::DurabilityMode::Strict
1475 ) {
1476 self.flush_paged_state()?;
1477 }
1478 }
1479
1480 Ok(())
1481 }
1482
1483 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1485 self.cross_refs.read().get(&id).cloned().unwrap_or_default()
1486 }
1487
1488 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1490 self.reverse_refs
1491 .read()
1492 .get(&id)
1493 .cloned()
1494 .unwrap_or_default()
1495 }
1496
1497 pub fn expand_refs(
1499 &self,
1500 id: EntityId,
1501 depth: u32,
1502 ref_types: Option<&[RefType]>,
1503 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1504 let mut results = Vec::new();
1505 let mut visited = std::collections::HashSet::new();
1506 visited.insert(id);
1507
1508 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1509
1510 results
1511 }
1512
1513 fn expand_refs_recursive(
1514 &self,
1515 id: EntityId,
1516 max_depth: u32,
1517 ref_types: Option<&[RefType]>,
1518 visited: &mut std::collections::HashSet<EntityId>,
1519 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1520 current_depth: u32,
1521 ) {
1522 if current_depth > max_depth {
1523 return;
1524 }
1525
1526 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1527 if visited.contains(&target_id) {
1528 continue;
1529 }
1530
1531 if let Some(types) = ref_types {
1532 if !types.contains(&ref_type) {
1533 continue;
1534 }
1535 }
1536
1537 visited.insert(target_id);
1538
1539 if let Some(entity) = self.get(&target_collection, target_id) {
1540 results.push((entity, current_depth, ref_type));
1541
1542 self.expand_refs_recursive(
1544 target_id,
1545 max_depth,
1546 ref_types,
1547 visited,
1548 results,
1549 current_depth + 1,
1550 );
1551 }
1552 }
1553 }
1554
1555 pub(crate) fn index_cross_refs(
1557 &self,
1558 entity: &UnifiedEntity,
1559 collection: &str,
1560 ) -> Result<(), StoreError> {
1561 let mut registry_dirty = false;
1562 for cross_ref in entity.cross_refs() {
1563 if cross_ref.target_collection.is_empty() {
1564 continue;
1565 }
1566 {
1567 let mut forward = self.cross_refs.write();
1568 let refs = forward.entry(cross_ref.source).or_default();
1569 let inserted = !refs.iter().any(|(id, kind, coll)| {
1570 *id == cross_ref.target
1571 && *kind == cross_ref.ref_type
1572 && coll == &cross_ref.target_collection
1573 });
1574 if inserted {
1575 refs.push((
1576 cross_ref.target,
1577 cross_ref.ref_type,
1578 cross_ref.target_collection.clone(),
1579 ));
1580 registry_dirty = true;
1581 }
1582 }
1583
1584 {
1585 let mut reverse = self.reverse_refs.write();
1586 let refs = reverse.entry(cross_ref.target).or_default();
1587 let inserted = !refs.iter().any(|(id, kind, coll)| {
1588 *id == cross_ref.source && *kind == cross_ref.ref_type && coll == collection
1589 });
1590 if inserted {
1591 refs.push((cross_ref.source, cross_ref.ref_type, collection.to_string()));
1592 registry_dirty = true;
1593 }
1594 }
1595 }
1596
1597 if registry_dirty {
1598 self.mark_paged_registry_dirty();
1599 }
1600
1601 Ok(())
1602 }
1603
1604 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1606 self.cross_refs.write().remove(&id);
1608
1609 let mut reverse = self.reverse_refs.write();
1611 for refs in reverse.values_mut() {
1612 refs.retain(|(source, _, _)| *source != id);
1613 }
1614 reverse.remove(&id);
1615 self.mark_paged_registry_dirty();
1616
1617 Ok(())
1618 }
1619
1620 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1621 if ids.is_empty() {
1622 return Ok(());
1623 }
1624
1625 let id_set: std::collections::HashSet<EntityId> = ids.iter().copied().collect();
1626
1627 let needs_forward_cleanup = {
1641 let forward = self.cross_refs.read();
1642 id_set.iter().any(|id| forward.contains_key(id))
1643 };
1644 let needs_reverse_cleanup = {
1645 let reverse = self.reverse_refs.read();
1646 id_set.iter().any(|id| reverse.contains_key(id))
1647 };
1648
1649 if !needs_forward_cleanup && !needs_reverse_cleanup {
1650 self.unindex_cross_refs_fast_path
1651 .fetch_add(1, Ordering::Relaxed);
1652 return Ok(());
1653 }
1654
1655 if needs_forward_cleanup {
1656 let mut forward = self.cross_refs.write();
1657 for id in &id_set {
1658 forward.remove(id);
1659 }
1660 }
1661
1662 if needs_reverse_cleanup || needs_forward_cleanup {
1663 let mut reverse = self.reverse_refs.write();
1667 if needs_forward_cleanup {
1668 for refs in reverse.values_mut() {
1669 refs.retain(|(source, _, _)| !id_set.contains(source));
1670 }
1671 }
1672 reverse.retain(|target, refs| !id_set.contains(target) && !refs.is_empty());
1673 }
1674 self.mark_paged_registry_dirty();
1675
1676 Ok(())
1677 }
1678
1679 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1683 self.unindex_cross_refs_fast_path.load(Ordering::Relaxed)
1684 }
1685
1686 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1688 where
1689 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1690 {
1691 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1702 let collections = self.collections.read();
1703 collections
1704 .iter()
1705 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1706 .collect()
1707 };
1708
1709 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1710 if !use_parallel {
1711 return pairs
1713 .into_iter()
1714 .flat_map(|(name, mgr)| {
1715 mgr.query_all(filter.clone())
1716 .into_iter()
1717 .map(move |e| (name.clone(), e))
1718 })
1719 .collect();
1720 }
1721
1722 let filter_ref = &filter;
1724 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|s| {
1725 pairs
1726 .iter()
1727 .map(|(name, manager)| {
1728 let name = (*name).clone();
1729 s.spawn(move || {
1730 manager
1731 .query_all(|e| filter_ref(e))
1732 .into_iter()
1733 .map(|e| (name.clone(), e))
1734 .collect::<Vec<_>>()
1735 })
1736 })
1737 .collect::<Vec<_>>()
1738 .into_iter()
1739 .map(|h| h.join().unwrap_or_default())
1740 .collect()
1741 });
1742
1743 collection_results.into_iter().flatten().collect()
1744 }
1745
1746 pub fn scan<F>(
1748 &self,
1749 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1750 filter: F,
1751 ) -> Vec<(String, UnifiedEntity)>
1752 where
1753 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1754 {
1755 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1756 let collections = self.collections.read();
1757 collections
1758 .iter()
1759 .map(|(name, manager)| (name.clone(), Arc::clone(manager)))
1760 .collect()
1761 };
1762
1763 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1764 if !use_parallel {
1765 return pairs
1766 .into_iter()
1767 .flat_map(|(name, manager)| {
1768 manager
1769 .scan(snapshot, filter.clone())
1770 .into_iter()
1771 .map(move |entity| (name.clone(), entity))
1772 })
1773 .collect();
1774 }
1775
1776 let filter_ref = &filter;
1777 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|scope| {
1778 pairs
1779 .iter()
1780 .map(|(name, manager)| {
1781 let name = name.clone();
1782 scope.spawn(move || {
1783 manager
1784 .scan(snapshot, |entity| filter_ref(entity))
1785 .into_iter()
1786 .map(|entity| (name.clone(), entity))
1787 .collect::<Vec<_>>()
1788 })
1789 })
1790 .collect::<Vec<_>>()
1791 .into_iter()
1792 .map(|handle| handle.join().unwrap_or_default())
1793 .collect()
1794 });
1795
1796 collection_results.into_iter().flatten().collect()
1797 }
1798
1799 pub fn filter_metadata_all(
1801 &self,
1802 filters: &[(String, MetadataFilter)],
1803 ) -> Vec<(String, EntityId)> {
1804 let mut results = Vec::new();
1805 let collections = self.collections.read();
1806
1807 for (name, manager) in collections.iter() {
1808 for id in manager.filter_metadata(filters) {
1809 results.push((name.clone(), id));
1810 }
1811 }
1812
1813 results
1814 }
1815
1816 pub fn stats(&self) -> StoreStats {
1818 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1824 let collections = self.collections.read();
1825 collections
1826 .iter()
1827 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1828 .collect()
1829 };
1830
1831 let mut stats = StoreStats {
1832 collection_count: pairs.len(),
1833 ..Default::default()
1834 };
1835
1836 for (name, manager) in &pairs {
1837 let manager_stats = manager.stats();
1838 stats.total_entities += manager_stats.total_entities;
1839 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1840 stats.total_memory_bytes += manager.checkpoint_tracking_bytes();
1841 stats.collections.insert(name.clone(), manager_stats);
1842 }
1843
1844 stats.total_memory_bytes += self.checkpoint_durable_rows.read().memory_bytes();
1845 stats.total_memory_bytes += self
1846 .checkpoint_versions
1847 .read()
1848 .values()
1849 .filter_map(|version| version.deferred_rows.as_ref())
1850 .map(|rows| rows.capacity() * std::mem::size_of::<EntityId>())
1851 .sum::<usize>();
1852 stats
1853 }
1854
1855 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1857 let collections = self.collections.read();
1858 for manager in collections.values() {
1859 manager.run_maintenance()?;
1860 }
1861 Ok(())
1862 }
1863
1864 pub fn reclaimable_segment_bytes(&self) -> u64 {
1866 let collections = self.collections.read();
1867 collections
1868 .values()
1869 .map(|manager| manager.reclaimable_bytes())
1870 .fold(0, u64::saturating_add)
1871 }
1872
1873 pub fn pressure_consolidation_tick(&self) -> bool {
1876 let managers: Vec<_> = self.collections.read().values().cloned().collect();
1877 let mut advanced = false;
1880 for manager in &managers {
1881 advanced |= manager.pressure_consolidation_tick();
1882 }
1883 advanced
1884 }
1885}
1886
1887fn flatten_config_json(
1889 prefix: &str,
1890 value: &crate::serde_json::Value,
1891 out: &mut Vec<(String, reddb_types::Value)>,
1892) {
1893 use reddb_types::Value;
1894 match value {
1895 crate::serde_json::Value::Object(map) => {
1896 for (k, v) in map {
1897 let key = if prefix.is_empty() {
1898 k.clone()
1899 } else {
1900 format!("{prefix}.{k}")
1901 };
1902 flatten_config_json(&key, v, out);
1903 }
1904 }
1905 crate::serde_json::Value::String(s) => {
1906 out.push((prefix.to_string(), Value::text(s.clone())));
1907 }
1908 crate::serde_json::Value::Integer(n) => {
1909 out.push((prefix.to_string(), Value::Integer(*n)));
1910 }
1911 crate::serde_json::Value::Number(n) => {
1912 if n.fract().abs() < f64::EPSILON {
1913 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1914 } else {
1915 out.push((prefix.to_string(), Value::Float(*n)));
1916 }
1917 }
1918 crate::serde_json::Value::Decimal(n) => {
1919 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1920 }
1921 crate::serde_json::Value::Bool(b) => {
1922 out.push((prefix.to_string(), Value::Boolean(*b)));
1923 }
1924 crate::serde_json::Value::Null => {
1925 out.push((prefix.to_string(), Value::Null));
1926 }
1927 crate::serde_json::Value::Array(arr) => {
1928 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1929 out.push((prefix.to_string(), Value::text(json_str)));
1930 }
1931 }
1932}