1use super::*;
2
3impl UnifiedStore {
4 pub(crate) fn persist_entities_to_pager(
5 &self,
6 collection: &str,
7 entities: &[UnifiedEntity],
8 ) -> Result<(), StoreError> {
9 self.persist_entities_impl(collection, entities, false)
10 }
11
12 pub(crate) fn persist_entity_refs_to_pager(
16 &self,
17 collection: &str,
18 entities: &[&UnifiedEntity],
19 ) -> Result<(), StoreError> {
20 self.persist_entity_refs_impl(collection, entities, false)
21 }
22
23 pub(crate) fn persist_entity_refs_to_pager_wal_only(
26 &self,
27 collection: &str,
28 entities: &[&UnifiedEntity],
29 ) -> Result<(), StoreError> {
30 self.persist_entity_refs_impl(collection, entities, true)
31 }
32
33 fn persist_entity_refs_impl(
34 &self,
35 collection: &str,
36 entities: &[&UnifiedEntity],
37 skip_btree: bool,
38 ) -> Result<(), StoreError> {
39 if entities.is_empty() {
40 return Ok(());
41 }
42 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
43 return Ok(());
44 }
45 let fv = self.format_version();
46 let manager = self
47 .get_collection(collection)
48 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
49 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
50 .iter()
51 .map(|entity| {
52 let metadata = manager.get_metadata(entity.id);
53 (
54 entity.id.raw().to_be_bytes().to_vec(),
55 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
56 )
57 })
58 .collect();
59 serialized.sort_by(|a, b| a.0.cmp(&b.0));
60
61 if !skip_btree {
62 let Some(pager) = &self.pager else {
63 let records: Vec<Vec<u8>> =
64 serialized.into_iter().map(|(_id, record)| record).collect();
65 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
66 collection: collection.to_string(),
67 records,
68 }])?;
69 return Ok(());
70 };
71 let mut btree_indices = self.btree_indices.write();
72 let btree = btree_indices
73 .entry(collection.to_string())
74 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
75 let root_before = btree.root_page_id();
76 btree.upsert_batch_sorted(&serialized).map_err(|e| {
77 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
78 })?;
79 let root_after = btree.root_page_id();
80 drop(btree_indices);
81 if root_before != root_after {
82 self.mark_paged_registry_dirty();
83 }
84 }
85 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
86 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
87 collection: collection.to_string(),
88 records,
89 }])?;
90 Ok(())
91 }
92
93 pub(crate) fn persist_entities_to_pager_wal_only(
105 &self,
106 collection: &str,
107 entities: &[UnifiedEntity],
108 ) -> Result<(), StoreError> {
109 self.persist_entities_impl(collection, entities, true)
110 }
111
112 fn persist_entities_impl(
113 &self,
114 collection: &str,
115 entities: &[UnifiedEntity],
116 skip_btree: bool,
117 ) -> Result<(), StoreError> {
118 if entities.is_empty() {
119 return Ok(());
120 }
121
122 if self.pager.is_none() && self.config.embedded_wal_path.is_none() {
123 return Ok(());
124 }
125
126 let fv = self.format_version();
127 let manager = self
128 .get_collection(collection)
129 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
130 let mut serialized: Vec<(Vec<u8>, Vec<u8>)> = entities
131 .iter()
132 .map(|entity| {
133 let metadata = manager.get_metadata(entity.id);
134 (
135 entity.id.raw().to_be_bytes().to_vec(),
136 Self::serialize_entity_record(entity, metadata.as_ref(), fv),
137 )
138 })
139 .collect();
140 serialized.sort_by(|a, b| a.0.cmp(&b.0));
142
143 if !skip_btree {
144 let Some(pager) = &self.pager else {
145 let records: Vec<Vec<u8>> =
146 serialized.into_iter().map(|(_id, record)| record).collect();
147 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
148 collection: collection.to_string(),
149 records,
150 }])?;
151 return Ok(());
152 };
153 let mut btree_indices = self.btree_indices.write();
154 let btree = btree_indices
155 .entry(collection.to_string())
156 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
157 let root_before = btree.root_page_id();
158
159 btree.upsert_batch_sorted(&serialized).map_err(|e| {
163 StoreError::Io(std::io::Error::other(format!("B-tree upsert error: {}", e)))
164 })?;
165 let root_after = btree.root_page_id();
166 drop(btree_indices);
167 if root_before != root_after {
168 self.mark_paged_registry_dirty();
169 }
170 }
171 let records: Vec<Vec<u8>> = serialized.into_iter().map(|(_id, record)| record).collect();
181 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
182 collection: collection.to_string(),
183 records,
184 }])?;
185
186 Ok(())
187 }
188
189 pub(crate) fn update_graph_label_index(
191 &self,
192 collection: &str,
193 label: &str,
194 entity_id: EntityId,
195 ) {
196 let key = (collection.to_string(), label.to_string());
197 let mut idx = self.graph_label_index.write();
198 idx.entry(key).or_default().push(entity_id);
199 }
200
201 pub(crate) fn remove_from_graph_label_index(&self, collection: &str, entity_id: EntityId) {
203 let mut idx = self.graph_label_index.write();
204 for ((col, _), ids) in idx.iter_mut() {
205 if col == collection {
206 ids.retain(|&id| id != entity_id);
207 }
208 }
209 idx.retain(|_, ids| !ids.is_empty());
211 }
212
213 pub(crate) fn remove_from_graph_label_index_batch(
214 &self,
215 collection: &str,
216 entity_ids: &[EntityId],
217 ) {
218 if entity_ids.is_empty() {
219 return;
220 }
221 let id_set: std::collections::HashSet<EntityId> = entity_ids.iter().copied().collect();
222 let mut idx = self.graph_label_index.write();
223 for ((col, _), ids) in idx.iter_mut() {
224 if col == collection {
225 ids.retain(|id| !id_set.contains(id));
226 }
227 }
228 idx.retain(|_, ids| !ids.is_empty());
229 }
230
231 pub fn lookup_graph_nodes_by_label(&self, label: &str) -> Vec<EntityId> {
233 let idx = self.graph_label_index.read();
234 idx.iter()
235 .filter(|((_, l), _)| l == label)
236 .flat_map(|(_, ids)| ids.iter().copied())
237 .collect()
238 }
239
240 pub fn lookup_graph_nodes_by_label_in(&self, collection: &str, label: &str) -> Vec<EntityId> {
242 let idx = self.graph_label_index.read();
243 idx.get(&(collection.to_string(), label.to_string()))
244 .cloned()
245 .unwrap_or_default()
246 }
247
248 pub fn create_collection(&self, name: impl Into<String>) -> Result<(), StoreError> {
249 let name = name.into();
250 let mut collections = self.collections.write();
251
252 if collections.contains_key(&name) {
253 return Err(StoreError::CollectionExists(name));
254 }
255
256 let manager = SegmentManager::with_config(&name, self.config.manager_config.clone());
257 collections.insert(name.clone(), Arc::new(manager));
258 drop(collections);
259
260 let create = match self.prepare_operational_collection_create(&name) {
261 Ok(create) => create,
262 Err(err) => {
263 let mut collections = self.collections.write();
264 collections.remove(&name);
265 return Err(err);
266 }
267 };
268 if let Some(create) = create {
269 if let Err(err) = create.publish().map_err(StoreError::Io) {
270 let mut collections = self.collections.write();
271 collections.remove(&name);
272 return Err(err);
273 }
274 }
275
276 self.mark_paged_registry_dirty();
277 self.finish_paged_write([StoreWalAction::CreateCollection { name }])?;
278
279 Ok(())
280 }
281
282 pub fn get_or_create_collection(&self, name: impl Into<String>) -> Arc<SegmentManager> {
284 let name = name.into();
285 {
287 let collections = self.collections.read();
288 if let Some(manager) = collections.get(&name) {
289 return Arc::clone(manager);
290 }
291 }
292 let mut collections = self.collections.write();
294 if let Some(manager) = collections.get(&name) {
296 return Arc::clone(manager);
297 }
298 let manager = Arc::new(SegmentManager::with_config(
299 &name,
300 self.config.manager_config.clone(),
301 ));
302 collections.insert(name, Arc::clone(&manager));
303 self.mark_paged_registry_dirty();
304 manager
305 }
306
307 pub fn get_collection(&self, name: &str) -> Option<Arc<SegmentManager>> {
309 self.collections.read().get(name).map(Arc::clone)
310 }
311
312 pub fn context_index(&self) -> &ContextIndex {
314 &self.context_index
315 }
316
317 pub fn take_replayed_turbo_inserts(&self, collection: &str) -> Option<Vec<(u64, Vec<f32>)>> {
323 let mut map = self.replayed_turbo_inserts.lock();
324 map.remove(collection)
325 }
326
327 pub(crate) fn take_replayed_probabilistic_deltas(&self) -> Vec<(u8, u8, String, Vec<Vec<u8>>)> {
328 let mut deltas = self.replayed_probabilistic_deltas.lock();
329 std::mem::take(&mut *deltas)
330 }
331
332 pub fn set_config_tree(&self, prefix: &str, json: &crate::serde_json::Value) -> usize {
335 let _ = self.get_or_create_collection("red_config");
336 let mut pairs = Vec::new();
337 flatten_config_json(prefix, json, &mut pairs);
338 let mut saved = 0;
339 for (key, value) in pairs {
340 let entity = UnifiedEntity::new(
341 EntityId::new(0),
342 EntityKind::TableRow {
343 table: Arc::from("red_config"),
344 row_id: 0,
345 },
346 EntityData::Row(RowData {
347 columns: Vec::new(),
348 named: Some(
349 [
350 ("key".to_string(), reddb_types::Value::text(key)),
351 ("value".to_string(), value),
352 ]
353 .into_iter()
354 .collect(),
355 ),
356 schema: None,
357 }),
358 );
359 if self.insert_auto("red_config", entity).is_ok() {
360 saved += 1;
361 }
362 }
363 saved
364 }
365
366 pub fn get_config(&self, key: &str) -> Option<reddb_types::Value> {
368 let manager = self.get_collection("red_config")?;
369 for entity in manager.query_all(|_| true) {
370 if let EntityData::Row(row) = &entity.data {
371 if let Some(named) = &row.named {
372 let key_matches = named
373 .get("key")
374 .and_then(|v| match v {
375 reddb_types::Value::Text(s) => Some(s.as_ref() == key),
376 _ => None,
377 })
378 .unwrap_or(false);
379 if key_matches {
380 return named.get("value").cloned();
381 }
382 }
383 }
384 }
385 None
386 }
387
388 pub fn set_aux_metadata(&self, bytes: Vec<u8>) {
393 *self.aux_metadata.write() = bytes;
394 }
395
396 pub fn aux_metadata(&self) -> Vec<u8> {
398 self.aux_metadata.read().clone()
399 }
400
401 pub fn list_collections(&self) -> Vec<String> {
403 self.collections.read().keys().cloned().collect()
404 }
405
406 pub fn drop_collection(&self, name: &str) -> Result<(), StoreError> {
408 let drop = self.prepare_operational_collection_drop(name)?;
409 let manager = {
410 let mut collections = self.collections.write();
411
412 collections
413 .remove(name)
414 .ok_or_else(|| StoreError::CollectionNotFound(name.to_string()))?
415 };
416
417 let entities = manager.query_all(|_| true);
418 let entity_ids: Vec<EntityId> = entities.iter().map(|entity| entity.id).collect();
419
420 for entity_id in &entity_ids {
421 self.context_index.remove_entity(*entity_id);
422 let _ = self.unindex_cross_refs(*entity_id);
423 }
424
425 self.btree_indices.write().remove(name);
426
427 self.entity_cache.retain(|entity_id, (collection, _)| {
428 collection != name && !entity_ids.iter().any(|id| id.raw() == entity_id)
429 });
430
431 self.cross_refs.write().retain(|source_id, refs| {
432 refs.retain(|(target_id, _, target_collection)| {
433 target_collection != name && !entity_ids.iter().any(|id| id == target_id)
434 });
435 !entity_ids.iter().any(|id| id == source_id)
436 });
437
438 self.reverse_refs.write().retain(|target_id, refs| {
439 refs.retain(|(source_id, _, source_collection)| {
440 source_collection != name && !entity_ids.iter().any(|id| id == source_id)
441 });
442 !entity_ids.iter().any(|id| id == target_id)
443 });
444
445 self.mark_paged_registry_dirty();
446 self.finish_paged_write([StoreWalAction::DropCollection {
447 name: name.to_string(),
448 }])?;
449 if let Some(drop) = drop {
450 drop.finish().map_err(StoreError::Io)?;
451 }
452
453 Ok(())
454 }
455
456 pub fn insert(&self, collection: &str, entity: UnifiedEntity) -> Result<EntityId, StoreError> {
458 let manager = self
459 .get_collection(collection)
460 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
461
462 let mut entity = entity;
463 if entity.id.raw() == 0 {
464 entity.id = self.next_entity_id();
465 } else {
466 self.register_entity_id(entity.id);
467 }
468 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
470 if *row_id == 0 {
471 *row_id = manager.next_row_id();
472 } else {
473 manager.register_row_id(*row_id);
474 }
475 }
476 entity.ensure_table_logical_id();
477 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
479 {
480 Some(node.label.clone())
481 } else {
482 None
483 };
484
485 let id = manager.insert(entity)?;
486 self.register_entity_id(id);
487
488 if let Some(ref label) = graph_node_label {
490 self.update_graph_label_index(collection, label, id);
491 }
492
493 let mut registry_dirty = false;
495 if let Some(pager) = &self.pager {
496 if let Some(entity) = manager.get(id) {
497 let mut btree_indices = self.btree_indices.write();
498 let btree = btree_indices
499 .entry(collection.to_string())
500 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
501 let root_before = btree.root_page_id();
502
503 let key = id.raw().to_be_bytes();
504 let metadata = manager.get_metadata(id);
505 let value = Self::serialize_entity_record(
506 &entity,
507 metadata.as_ref(),
508 self.format_version(),
509 );
510 let _ = btree.insert(&key, &value);
512 registry_dirty = root_before != btree.root_page_id();
513 }
514 }
515
516 if self.config.auto_index_refs {
518 if let Some(entity) = manager.get(id) {
519 self.index_cross_refs(&entity, collection)?;
520 }
521 }
522
523 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
527 let actions = manager
528 .get(id)
529 .map(|entity| {
530 let metadata = manager.get_metadata(id);
531 vec![StoreWalAction::upsert_entity(
532 collection,
533 &entity,
534 metadata.as_ref(),
535 self.format_version(),
536 )]
537 })
538 .unwrap_or_default();
539 if registry_dirty {
540 self.mark_paged_registry_dirty();
541 }
542 self.finish_paged_write(actions)?;
543 }
544
545 Ok(id)
546 }
547
548 pub fn bulk_insert(
553 &self,
554 collection: &str,
555 mut entities: Vec<UnifiedEntity>,
556 ) -> Result<Vec<EntityId>, StoreError> {
557 let trace = matches!(
561 std::env::var("REDDB_BULK_TIMING").ok().as_deref(),
562 Some("1") | Some("true") | Some("on")
563 );
564 let t_start = std::time::Instant::now();
565 let n = entities.len();
566 let manager = self.get_or_create_collection(collection);
567 let t_get_coll = t_start.elapsed();
568
569 let t0 = std::time::Instant::now();
578 let n_missing_entity_ids = entities.iter().filter(|e| e.id.raw() == 0).count() as u64;
579 let n_missing_row_ids = entities
580 .iter()
581 .filter(|e| matches!(e.kind, EntityKind::TableRow { row_id: 0, .. }))
582 .count() as u64;
583 let mut entity_id_range = if n_missing_entity_ids > 0 {
584 self.reserve_entity_ids(n_missing_entity_ids)
585 } else {
586 0..0
587 };
588 let mut row_id_range = if n_missing_row_ids > 0 {
589 manager.reserve_row_ids(n_missing_row_ids)
590 } else {
591 0..0
592 };
593 for entity in &mut entities {
594 if entity.id.raw() == 0 {
595 let next = entity_id_range
596 .next()
597 .expect("reserved entity-id range exhausted");
598 entity.id = EntityId::new(next);
599 } else {
600 self.register_entity_id(entity.id);
601 }
602 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
603 if *row_id == 0 {
604 *row_id = row_id_range
605 .next()
606 .expect("reserved row-id range exhausted");
607 } else {
608 manager.register_row_id(*row_id);
609 }
610 }
611 entity.ensure_table_logical_id();
612 }
613 let t_assign_ids = t0.elapsed();
614
615 let graph_labels: Vec<Option<(String, EntityId)>> = entities
617 .iter()
618 .map(|e| {
619 if let EntityKind::GraphNode(ref node) = e.kind {
620 Some((node.label.clone(), e.id))
621 } else {
622 None
623 }
624 })
625 .collect();
626
627 let t0 = std::time::Instant::now();
634 let serialized: Option<Vec<(Vec<u8>, Vec<u8>)>> =
635 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
636 let fv = self.format_version();
637 let serial_map = |e: &UnifiedEntity| {
638 (
639 e.id.raw().to_be_bytes().to_vec(),
640 Self::serialize_entity_record(e, None, fv),
641 )
642 };
643 if entities.len() >= 512 {
649 use rayon::prelude::*;
650 Some(entities.par_iter().map(serial_map).collect())
651 } else {
652 Some(entities.iter().map(serial_map).collect())
653 }
654 } else {
655 None
656 };
657 let t_serialize = t0.elapsed();
658
659 let t0 = std::time::Instant::now();
661 let ids = manager.bulk_insert(entities)?;
662 let t_manager = t0.elapsed();
663 for id in &ids {
664 self.register_entity_id(*id);
665 }
666
667 for (label, entity_id) in graph_labels.iter().flatten() {
669 self.update_graph_label_index(collection, label, *entity_id);
670 }
671
672 let skip_btree_requested = matches!(
682 std::env::var("REDDB_BULK_SKIP_PERSIST_UNSAFE")
683 .ok()
684 .as_deref(),
685 Some("1") | Some("true") | Some("on")
686 );
687 let skip_btree = skip_btree_requested && self.pager.is_none();
690 if skip_btree_requested && !skip_btree {
691 static IGNORED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
693 IGNORED.get_or_init(|| {
694 tracing::warn!(
695 "REDDB_BULK_SKIP_PERSIST_UNSAFE set but durable pager is \
696 active — flag ignored; bulk inserts will be persisted normally"
697 );
698 });
699 } else if skip_btree {
700 static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
702 WARNED.get_or_init(|| {
703 tracing::warn!(
704 "REDDB_BULK_SKIP_PERSIST_UNSAFE set (ephemeral/no-pager mode) — \
705 bulk inserts NOT durable; data will be lost on restart"
706 );
707 });
708 }
709
710 let mut t_btree_lock = std::time::Duration::ZERO;
714 let mut t_btree_insert = std::time::Duration::ZERO;
715 let mut t_flush = std::time::Duration::ZERO;
716 if !skip_btree {
717 if let (Some(pager), Some(batch)) = (&self.pager, serialized.as_ref()) {
718 let t0 = std::time::Instant::now();
719 let mut btree_indices = self.btree_indices.write();
720 let btree = btree_indices
721 .entry(collection.to_string())
722 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))));
723 let root_before = btree.root_page_id();
724 t_btree_lock = t0.elapsed();
725
726 let t0 = std::time::Instant::now();
727 let _ = btree.bulk_insert_sorted(batch);
728 t_btree_insert = t0.elapsed();
729 let registry_dirty = root_before != btree.root_page_id();
730
731 let t0 = std::time::Instant::now();
732 if registry_dirty {
733 self.mark_paged_registry_dirty();
734 }
735 t_flush = t0.elapsed();
736 }
737 }
738
739 let actions = serialized
745 .map(|batch| {
746 let records: Vec<Vec<u8>> =
747 batch.into_iter().map(|(_key, record)| record).collect();
748 vec![StoreWalAction::BulkUpsertEntityRecords {
749 collection: collection.to_string(),
750 records,
751 }]
752 })
753 .unwrap_or_default();
754 self.finish_paged_write(actions)?;
755
756 if trace {
757 tracing::debug!(
758 n,
759 total = ?t_start.elapsed(),
760 get_coll = ?t_get_coll,
761 assign = ?t_assign_ids,
762 serialize = ?t_serialize,
763 manager = ?t_manager,
764 btree_lock = ?t_btree_lock,
765 btree = ?t_btree_insert,
766 flush = ?t_flush,
767 "bulk_insert timing"
768 );
769 }
770
771 Ok(ids)
772 }
773
774 pub fn insert_auto(
776 &self,
777 collection: &str,
778 entity: UnifiedEntity,
779 ) -> Result<EntityId, StoreError> {
780 let manager = self.get_or_create_collection(collection);
781 let mut entity = entity;
782 if entity.id.raw() == 0 {
783 entity.id = self.next_entity_id();
784 } else {
785 self.register_entity_id(entity.id);
786 }
787 if let EntityKind::TableRow { ref mut row_id, .. } = entity.kind {
789 if *row_id == 0 {
790 *row_id = manager.next_row_id();
791 } else {
792 manager.register_row_id(*row_id);
793 }
794 }
795 entity.ensure_table_logical_id();
796
797 let graph_node_label: Option<String> = if let EntityKind::GraphNode(ref node) = entity.kind
799 {
800 Some(node.label.clone())
801 } else {
802 None
803 };
804 self.context_index.index_entity(collection, &entity);
806
807 let id_for_serialize = entity.id;
822 let serialized_record: Option<Vec<u8>> =
823 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
824 Some(Self::serialize_entity_record(
825 &entity,
826 None,
827 self.format_version(),
828 ))
829 } else {
830 None
831 };
832 if self.config.auto_index_refs {
833 self.index_cross_refs(&entity, collection)?;
834 }
835
836 let id = manager.insert(entity)?;
837 debug_assert_eq!(id, id_for_serialize);
838 if let Some(ref label) = graph_node_label {
846 self.update_graph_label_index(collection, label, id);
847 }
848
849 let mut registry_dirty = false;
850 if let (Some(_pager), Some(record)) = (&self.pager, serialized_record.as_ref()) {
851 if let Some(btree) = self.get_or_create_btree(collection) {
852 let root_before = btree.root_page_id();
853
854 let key = id.raw().to_be_bytes();
855 btree.insert(&key, record).map_err(|e| {
856 StoreError::Io(std::io::Error::other(format!(
857 "B-tree insert error while inserting '{collection}'/{id}: {e}"
858 )))
859 })?;
860 registry_dirty = root_before != btree.root_page_id();
861 }
862 }
863
864 if self.pager.is_some() || self.config.embedded_wal_path.is_some() {
868 let actions = serialized_record
869 .map(|record| {
870 vec![StoreWalAction::UpsertEntityRecord {
871 collection: collection.to_string(),
872 record,
873 }]
874 })
875 .unwrap_or_default();
876 if registry_dirty {
877 self.mark_paged_registry_dirty();
878 }
879 self.finish_paged_write(actions)?;
880 }
881 Ok(id)
882 }
883
884 pub fn get(&self, collection: &str, id: EntityId) -> Option<UnifiedEntity> {
890 if let Some(entity) = self
892 .get_collection(collection)
893 .and_then(|manager| manager.get(id))
894 {
895 return Some(entity);
896 }
897
898 if self.pager.is_some() {
900 let btree_indices = self.btree_indices.read();
901 if let Some(btree) = btree_indices.get(collection) {
902 let key = id.raw().to_be_bytes();
903 if let Ok(Some(value)) = btree.get(&key) {
904 if let Ok((entity, _)) =
905 Self::deserialize_entity_record(&value, self.format_version())
906 {
907 return Some(entity);
908 }
909 }
910 }
911 }
912
913 None
914 }
915
916 pub fn get_table_row_by_logical_id(
922 &self,
923 collection: &str,
924 logical_id: EntityId,
925 ) -> Option<UnifiedEntity> {
926 if let Some(entity) = self.get(collection, logical_id) {
927 if matches!(entity.kind, EntityKind::TableRow { .. })
928 && entity.logical_id() == logical_id
929 && entity.xmax == 0
930 {
931 return Some(entity);
932 }
933 }
934
935 let manager = self.get_collection(collection)?;
936 let mut matches = manager.query_all(|entity| {
937 matches!(entity.kind, EntityKind::TableRow { .. }) && entity.logical_id() == logical_id
938 });
939 matches
940 .iter()
941 .find(|entity| entity.xmax == 0)
942 .cloned()
943 .or_else(|| matches.pop())
944 }
945
946 pub fn table_row_versions_by_logical_id(
947 &self,
948 collection: &str,
949 logical_id: EntityId,
950 ) -> Vec<UnifiedEntity> {
951 self.get_collection(collection)
952 .map(|manager| {
953 manager.query_all(|entity| {
954 matches!(entity.kind, EntityKind::TableRow { .. })
955 && entity.logical_id() == logical_id
956 })
957 })
958 .unwrap_or_default()
959 }
960
961 pub fn vacuum_mvcc_history(
962 &self,
963 collection: &str,
964 cutoff_xid: u64,
965 ) -> Result<MvccVacuumStats, StoreError> {
966 let manager = self
967 .get_collection(collection)
968 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
969 let entities =
970 manager.query_all(|entity| matches!(entity.kind, EntityKind::TableRow { .. }));
971 let mut logical = HashMap::<EntityId, (bool, u64)>::new();
972 for entity in &entities {
973 let entry = logical.entry(entity.logical_id()).or_insert((false, 0));
974 if entity.xmax == 0 {
975 entry.0 = true;
976 }
977 entry.1 = entry.1.max(entity.xmin);
978 }
979
980 let mut stats = MvccVacuumStats::default();
981 let mut reclaim_ids = Vec::new();
982 for entity in entities {
983 if entity.xmax == 0 {
984 continue;
985 }
986 stats.scanned_versions += 1;
987 let (has_live_version, max_xmin) = logical
988 .get(&entity.logical_id())
989 .copied()
990 .unwrap_or((false, entity.xmin));
991 let is_delete_tombstone = !has_live_version && entity.xmin == max_xmin;
992 if entity.xmax < cutoff_xid {
993 stats.reclaimed_versions += 1;
994 if is_delete_tombstone {
995 stats.reclaimed_tombstones += 1;
996 } else {
997 stats.reclaimed_history_versions += 1;
998 }
999 reclaim_ids.push(entity.id);
1000 } else {
1001 stats.retained_versions += 1;
1002 if is_delete_tombstone {
1003 stats.retained_tombstones += 1;
1004 } else {
1005 stats.retained_history_versions += 1;
1006 }
1007 }
1008 }
1009
1010 if !reclaim_ids.is_empty() {
1011 self.delete_batch(collection, &reclaim_ids)?;
1012 }
1013 Ok(stats)
1014 }
1015
1016 pub(crate) fn install_versioned_table_row_update(
1017 &self,
1018 collection: &str,
1019 old_version: UnifiedEntity,
1020 mut new_version: UnifiedEntity,
1021 metadata: Option<&Metadata>,
1022 ) -> Result<(), StoreError> {
1023 let manager = self
1024 .get_collection(collection)
1025 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1026
1027 let old_id = old_version.id;
1028 let new_id = new_version.id;
1029 let inherited_metadata = metadata.cloned().or_else(|| manager.get_metadata(old_id));
1030
1031 self.entity_cache.remove(old_id.raw());
1032 self.entity_cache.remove(new_id.raw());
1033 manager.update(old_version.clone())?;
1034 self.context_index.remove_entity(old_id);
1035
1036 self.register_entity_id(new_version.id);
1037 if let EntityKind::TableRow { ref mut row_id, .. } = new_version.kind {
1038 if *row_id == 0 {
1039 *row_id = manager.next_row_id();
1040 } else {
1041 manager.register_row_id(*row_id);
1042 }
1043 }
1044 new_version.ensure_table_logical_id();
1045 manager.insert(new_version.clone())?;
1046 if let Some(metadata) = inherited_metadata {
1047 manager.set_metadata(new_id, metadata)?;
1048 }
1049 self.context_index.index_entity(collection, &new_version);
1050 if self.config.auto_index_refs {
1051 self.index_cross_refs(&new_version, collection)?;
1052 }
1053
1054 let old_metadata = manager.get_metadata(old_id);
1055 let new_metadata = manager.get_metadata(new_id);
1056 let fv = self.format_version();
1057 let records = vec![
1058 Self::serialize_entity_record(&old_version, old_metadata.as_ref(), fv),
1059 Self::serialize_entity_record(&new_version, new_metadata.as_ref(), fv),
1060 ];
1061 self.finish_paged_write([StoreWalAction::BulkUpsertEntityRecords {
1062 collection: collection.to_string(),
1063 records,
1064 }])?;
1065
1066 Ok(())
1067 }
1068
1069 pub fn get_batch(&self, collection: &str, ids: &[EntityId]) -> Vec<Option<UnifiedEntity>> {
1075 match self.get_collection(collection) {
1076 Some(manager) => manager.get_many(ids),
1077 None => vec![None; ids.len()],
1078 }
1079 }
1080
1081 pub fn get_any(&self, id: EntityId) -> Option<(String, UnifiedEntity)> {
1083 if let Some(cached) = self.entity_cache.get(id.raw()) {
1087 return Some(cached);
1088 }
1089
1090 let collections = self.collections.read();
1092 for (name, manager) in collections.iter() {
1093 if let Some(entity) = manager.get(id) {
1094 let result = (name.clone(), entity);
1095 drop(collections);
1099 self.entity_cache.insert(id.raw(), result.clone());
1100 return Some(result);
1101 }
1102 }
1103 None
1104 }
1105
1106 pub fn entity_cache_hit_rate(&self) -> Option<f64> {
1112 self.entity_cache.hit_rate()
1113 }
1114
1115 pub fn entity_cache_stats(&self) -> super::super::entity_cache::EntityCacheStats {
1117 self.entity_cache.stats()
1118 }
1119
1120 pub fn delete(&self, collection: &str, id: EntityId) -> Result<bool, StoreError> {
1122 self.entity_cache.remove(id.raw());
1124 let manager = self
1125 .get_collection(collection)
1126 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1127
1128 let deleted = manager.delete(id)?;
1129 if !deleted {
1130 return Ok(false);
1131 }
1132
1133 let mut registry_dirty = false;
1135 if self.pager.is_some() {
1136 let btree_indices = self.btree_indices.read();
1137 if let Some(btree) = btree_indices.get(collection) {
1138 let root_before = btree.root_page_id();
1139 let key = id.raw().to_be_bytes();
1140 let _ = btree.delete(&key);
1141 registry_dirty = root_before != btree.root_page_id();
1142 }
1143 }
1144
1145 self.unindex_cross_refs(id)?;
1147
1148 self.remove_from_graph_label_index(collection, id);
1150
1151 if registry_dirty {
1152 self.mark_paged_registry_dirty();
1153 }
1154 self.finish_paged_write([StoreWalAction::DeleteEntityRecord {
1155 collection: collection.to_string(),
1156 entity_id: id.raw(),
1157 }])?;
1158
1159 Ok(true)
1160 }
1161
1162 pub fn delete_batch(
1163 &self,
1164 collection: &str,
1165 ids: &[EntityId],
1166 ) -> Result<Vec<EntityId>, StoreError> {
1167 if ids.is_empty() {
1168 return Ok(Vec::new());
1169 }
1170
1171 self.entity_cache.remove_many(ids.iter().map(|id| id.raw()));
1176
1177 let manager = self
1178 .get_collection(collection)
1179 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1180
1181 let deleted_ids = manager.delete_batch(ids)?;
1182 if deleted_ids.is_empty() {
1183 return Ok(deleted_ids);
1184 }
1185
1186 let mut registry_dirty = false;
1187 if self.pager.is_some() {
1188 let btree_indices = self.btree_indices.read();
1189 if let Some(btree) = btree_indices.get(collection) {
1190 let root_before = btree.root_page_id();
1191 for id in &deleted_ids {
1192 let key = id.raw().to_be_bytes();
1193 let _ = btree.delete(&key);
1194 }
1195 registry_dirty = root_before != btree.root_page_id();
1196 }
1197 }
1198
1199 self.unindex_cross_refs_batch(&deleted_ids)?;
1200 self.remove_from_graph_label_index_batch(collection, &deleted_ids);
1201 if registry_dirty {
1202 self.mark_paged_registry_dirty();
1203 }
1204 let actions = deleted_ids
1205 .iter()
1206 .map(|id| StoreWalAction::DeleteEntityRecord {
1207 collection: collection.to_string(),
1208 entity_id: id.raw(),
1209 })
1210 .collect::<Vec<_>>();
1211 self.finish_paged_write(actions)?;
1212
1213 Ok(deleted_ids)
1214 }
1215
1216 pub fn set_metadata(
1218 &self,
1219 collection: &str,
1220 id: EntityId,
1221 metadata: Metadata,
1222 ) -> Result<(), StoreError> {
1223 let manager = self
1224 .get_collection(collection)
1225 .ok_or_else(|| StoreError::CollectionNotFound(collection.to_string()))?;
1226
1227 manager.set_metadata(id, metadata)?;
1228 if let Some(entity) = manager.get(id) {
1229 self.persist_entities_to_pager(collection, std::slice::from_ref(&entity))?;
1230 }
1231 Ok(())
1232 }
1233
1234 pub fn get_metadata(&self, collection: &str, id: EntityId) -> Option<Metadata> {
1236 self.get_collection(collection)?.get_metadata(id)
1237 }
1238
1239 pub fn add_cross_ref(
1241 &self,
1242 source_collection: &str,
1243 source_id: EntityId,
1244 target_collection: &str,
1245 target_id: EntityId,
1246 ref_type: RefType,
1247 weight: f32,
1248 ) -> Result<(), StoreError> {
1249 let source_manager = self
1251 .get_collection(source_collection)
1252 .ok_or_else(|| StoreError::CollectionNotFound(source_collection.to_string()))?;
1253
1254 if source_manager.get(source_id).is_none() {
1255 return Err(StoreError::EntityNotFound(source_id));
1256 }
1257
1258 let target_manager = self
1260 .get_collection(target_collection)
1261 .ok_or_else(|| StoreError::CollectionNotFound(target_collection.to_string()))?;
1262
1263 if target_manager.get(target_id).is_none() {
1264 return Err(StoreError::EntityNotFound(target_id));
1265 }
1266
1267 let current_refs = self
1269 .cross_refs
1270 .read()
1271 .get(&source_id)
1272 .map_or(0, |v| v.len());
1273
1274 if current_refs >= self.config.max_cross_refs {
1275 return Err(StoreError::TooManyRefs(source_id));
1276 }
1277
1278 let mut registry_dirty = false;
1279 {
1280 let mut forward = self.cross_refs.write();
1281 let refs = forward.entry(source_id).or_default();
1282 let inserted = !refs.iter().any(|(id, kind, coll)| {
1283 *id == target_id && *kind == ref_type && coll == target_collection
1284 });
1285 if inserted {
1286 refs.push((target_id, ref_type, target_collection.to_string()));
1287 registry_dirty = true;
1288 }
1289 }
1290
1291 {
1292 let mut reverse = self.reverse_refs.write();
1293 let refs = reverse.entry(target_id).or_default();
1294 let inserted = !refs.iter().any(|(id, kind, coll)| {
1295 *id == source_id && *kind == ref_type && coll == source_collection
1296 });
1297 if inserted {
1298 refs.push((source_id, ref_type, source_collection.to_string()));
1299 registry_dirty = true;
1300 }
1301 }
1302
1303 if let Some(mut entity) = source_manager.get(source_id) {
1304 if !entity.cross_refs().iter().any(|xref| {
1305 xref.target == target_id
1306 && xref.ref_type == ref_type
1307 && xref.target_collection == target_collection
1308 }) {
1309 let cross_ref = CrossRef::with_weight(
1310 source_id,
1311 target_id,
1312 target_collection,
1313 ref_type,
1314 weight,
1315 );
1316 entity.add_cross_ref(cross_ref);
1317 let _ = source_manager.update(entity.clone());
1318 registry_dirty = true;
1319 self.persist_entities_to_pager(source_collection, std::slice::from_ref(&entity))?;
1320 }
1321 }
1322
1323 if registry_dirty {
1324 self.mark_paged_registry_dirty();
1325 if matches!(
1326 self.config.durability_mode,
1327 crate::api::DurabilityMode::Strict
1328 ) {
1329 self.flush_paged_state()?;
1330 }
1331 }
1332
1333 Ok(())
1334 }
1335
1336 pub fn get_refs_from(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1338 self.cross_refs.read().get(&id).cloned().unwrap_or_default()
1339 }
1340
1341 pub fn get_refs_to(&self, id: EntityId) -> Vec<(EntityId, RefType, String)> {
1343 self.reverse_refs
1344 .read()
1345 .get(&id)
1346 .cloned()
1347 .unwrap_or_default()
1348 }
1349
1350 pub fn expand_refs(
1352 &self,
1353 id: EntityId,
1354 depth: u32,
1355 ref_types: Option<&[RefType]>,
1356 ) -> Vec<(UnifiedEntity, u32, RefType)> {
1357 let mut results = Vec::new();
1358 let mut visited = std::collections::HashSet::new();
1359 visited.insert(id);
1360
1361 self.expand_refs_recursive(id, depth, ref_types, &mut visited, &mut results, 1);
1362
1363 results
1364 }
1365
1366 fn expand_refs_recursive(
1367 &self,
1368 id: EntityId,
1369 max_depth: u32,
1370 ref_types: Option<&[RefType]>,
1371 visited: &mut std::collections::HashSet<EntityId>,
1372 results: &mut Vec<(UnifiedEntity, u32, RefType)>,
1373 current_depth: u32,
1374 ) {
1375 if current_depth > max_depth {
1376 return;
1377 }
1378
1379 for (target_id, ref_type, target_collection) in self.get_refs_from(id) {
1380 if visited.contains(&target_id) {
1381 continue;
1382 }
1383
1384 if let Some(types) = ref_types {
1385 if !types.contains(&ref_type) {
1386 continue;
1387 }
1388 }
1389
1390 visited.insert(target_id);
1391
1392 if let Some(entity) = self.get(&target_collection, target_id) {
1393 results.push((entity, current_depth, ref_type));
1394
1395 self.expand_refs_recursive(
1397 target_id,
1398 max_depth,
1399 ref_types,
1400 visited,
1401 results,
1402 current_depth + 1,
1403 );
1404 }
1405 }
1406 }
1407
1408 pub(crate) fn index_cross_refs(
1410 &self,
1411 entity: &UnifiedEntity,
1412 collection: &str,
1413 ) -> Result<(), StoreError> {
1414 let mut registry_dirty = false;
1415 for cross_ref in entity.cross_refs() {
1416 if cross_ref.target_collection.is_empty() {
1417 continue;
1418 }
1419 {
1420 let mut forward = self.cross_refs.write();
1421 let refs = forward.entry(cross_ref.source).or_default();
1422 let inserted = !refs.iter().any(|(id, kind, coll)| {
1423 *id == cross_ref.target
1424 && *kind == cross_ref.ref_type
1425 && coll == &cross_ref.target_collection
1426 });
1427 if inserted {
1428 refs.push((
1429 cross_ref.target,
1430 cross_ref.ref_type,
1431 cross_ref.target_collection.clone(),
1432 ));
1433 registry_dirty = true;
1434 }
1435 }
1436
1437 {
1438 let mut reverse = self.reverse_refs.write();
1439 let refs = reverse.entry(cross_ref.target).or_default();
1440 let inserted = !refs.iter().any(|(id, kind, coll)| {
1441 *id == cross_ref.source && *kind == cross_ref.ref_type && coll == collection
1442 });
1443 if inserted {
1444 refs.push((cross_ref.source, cross_ref.ref_type, collection.to_string()));
1445 registry_dirty = true;
1446 }
1447 }
1448 }
1449
1450 if registry_dirty {
1451 self.mark_paged_registry_dirty();
1452 }
1453
1454 Ok(())
1455 }
1456
1457 pub(crate) fn unindex_cross_refs(&self, id: EntityId) -> Result<(), StoreError> {
1459 self.cross_refs.write().remove(&id);
1461
1462 let mut reverse = self.reverse_refs.write();
1464 for refs in reverse.values_mut() {
1465 refs.retain(|(source, _, _)| *source != id);
1466 }
1467 reverse.remove(&id);
1468 self.mark_paged_registry_dirty();
1469
1470 Ok(())
1471 }
1472
1473 pub(crate) fn unindex_cross_refs_batch(&self, ids: &[EntityId]) -> Result<(), StoreError> {
1474 if ids.is_empty() {
1475 return Ok(());
1476 }
1477
1478 let id_set: std::collections::HashSet<EntityId> = ids.iter().copied().collect();
1479
1480 let needs_forward_cleanup = {
1494 let forward = self.cross_refs.read();
1495 id_set.iter().any(|id| forward.contains_key(id))
1496 };
1497 let needs_reverse_cleanup = {
1498 let reverse = self.reverse_refs.read();
1499 id_set.iter().any(|id| reverse.contains_key(id))
1500 };
1501
1502 if !needs_forward_cleanup && !needs_reverse_cleanup {
1503 self.unindex_cross_refs_fast_path
1504 .fetch_add(1, Ordering::Relaxed);
1505 return Ok(());
1506 }
1507
1508 if needs_forward_cleanup {
1509 let mut forward = self.cross_refs.write();
1510 for id in &id_set {
1511 forward.remove(id);
1512 }
1513 }
1514
1515 if needs_reverse_cleanup || needs_forward_cleanup {
1516 let mut reverse = self.reverse_refs.write();
1520 if needs_forward_cleanup {
1521 for refs in reverse.values_mut() {
1522 refs.retain(|(source, _, _)| !id_set.contains(source));
1523 }
1524 }
1525 reverse.retain(|target, refs| !id_set.contains(target) && !refs.is_empty());
1526 }
1527 self.mark_paged_registry_dirty();
1528
1529 Ok(())
1530 }
1531
1532 pub fn unindex_cross_refs_fast_path_hits(&self) -> u64 {
1536 self.unindex_cross_refs_fast_path.load(Ordering::Relaxed)
1537 }
1538
1539 pub fn query_all<F>(&self, filter: F) -> Vec<(String, UnifiedEntity)>
1541 where
1542 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1543 {
1544 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1555 let collections = self.collections.read();
1556 collections
1557 .iter()
1558 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1559 .collect()
1560 };
1561
1562 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1563 if !use_parallel {
1564 return pairs
1566 .into_iter()
1567 .flat_map(|(name, mgr)| {
1568 mgr.query_all(filter.clone())
1569 .into_iter()
1570 .map(move |e| (name.clone(), e))
1571 })
1572 .collect();
1573 }
1574
1575 let filter_ref = &filter;
1577 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|s| {
1578 pairs
1579 .iter()
1580 .map(|(name, manager)| {
1581 let name = (*name).clone();
1582 s.spawn(move || {
1583 manager
1584 .query_all(|e| filter_ref(e))
1585 .into_iter()
1586 .map(|e| (name.clone(), e))
1587 .collect::<Vec<_>>()
1588 })
1589 })
1590 .collect::<Vec<_>>()
1591 .into_iter()
1592 .map(|h| h.join().unwrap_or_default())
1593 .collect()
1594 });
1595
1596 collection_results.into_iter().flatten().collect()
1597 }
1598
1599 pub fn scan<F>(
1601 &self,
1602 snapshot: Option<&crate::runtime::impl_core::SnapshotContext>,
1603 filter: F,
1604 ) -> Vec<(String, UnifiedEntity)>
1605 where
1606 F: Fn(&UnifiedEntity) -> bool + Clone + Send + Sync,
1607 {
1608 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1609 let collections = self.collections.read();
1610 collections
1611 .iter()
1612 .map(|(name, manager)| (name.clone(), Arc::clone(manager)))
1613 .collect()
1614 };
1615
1616 let use_parallel = pairs.len() > 1 && crate::runtime::SystemInfo::should_parallelize();
1617 if !use_parallel {
1618 return pairs
1619 .into_iter()
1620 .flat_map(|(name, manager)| {
1621 manager
1622 .scan(snapshot, filter.clone())
1623 .into_iter()
1624 .map(move |entity| (name.clone(), entity))
1625 })
1626 .collect();
1627 }
1628
1629 let filter_ref = &filter;
1630 let collection_results: Vec<Vec<(String, UnifiedEntity)>> = std::thread::scope(|scope| {
1631 pairs
1632 .iter()
1633 .map(|(name, manager)| {
1634 let name = name.clone();
1635 scope.spawn(move || {
1636 manager
1637 .scan(snapshot, |entity| filter_ref(entity))
1638 .into_iter()
1639 .map(|entity| (name.clone(), entity))
1640 .collect::<Vec<_>>()
1641 })
1642 })
1643 .collect::<Vec<_>>()
1644 .into_iter()
1645 .map(|handle| handle.join().unwrap_or_default())
1646 .collect()
1647 });
1648
1649 collection_results.into_iter().flatten().collect()
1650 }
1651
1652 pub fn filter_metadata_all(
1654 &self,
1655 filters: &[(String, MetadataFilter)],
1656 ) -> Vec<(String, EntityId)> {
1657 let mut results = Vec::new();
1658 let collections = self.collections.read();
1659
1660 for (name, manager) in collections.iter() {
1661 for id in manager.filter_metadata(filters) {
1662 results.push((name.clone(), id));
1663 }
1664 }
1665
1666 results
1667 }
1668
1669 pub fn stats(&self) -> StoreStats {
1671 let pairs: Vec<(String, Arc<SegmentManager>)> = {
1677 let collections = self.collections.read();
1678 collections
1679 .iter()
1680 .map(|(name, mgr)| (name.clone(), Arc::clone(mgr)))
1681 .collect()
1682 };
1683
1684 let mut stats = StoreStats {
1685 collection_count: pairs.len(),
1686 ..Default::default()
1687 };
1688
1689 for (name, manager) in &pairs {
1690 let manager_stats = manager.stats();
1691 stats.total_entities += manager_stats.total_entities;
1692 stats.total_memory_bytes += manager_stats.total_memory_bytes;
1693 stats.collections.insert(name.clone(), manager_stats);
1694 }
1695
1696 stats
1697 }
1698
1699 pub fn run_maintenance(&self) -> Result<(), StoreError> {
1701 let collections = self.collections.read();
1702 for manager in collections.values() {
1703 manager.run_maintenance()?;
1704 }
1705 Ok(())
1706 }
1707
1708 pub fn reclaimable_segment_bytes(&self) -> u64 {
1710 let collections = self.collections.read();
1711 collections
1712 .values()
1713 .map(|manager| manager.reclaimable_bytes())
1714 .fold(0, u64::saturating_add)
1715 }
1716
1717 pub fn pressure_consolidation_tick(&self) -> bool {
1720 let managers: Vec<_> = self.collections.read().values().cloned().collect();
1721 let mut advanced = false;
1724 for manager in &managers {
1725 advanced |= manager.pressure_consolidation_tick();
1726 }
1727 advanced
1728 }
1729}
1730
1731fn flatten_config_json(
1733 prefix: &str,
1734 value: &crate::serde_json::Value,
1735 out: &mut Vec<(String, reddb_types::Value)>,
1736) {
1737 use reddb_types::Value;
1738 match value {
1739 crate::serde_json::Value::Object(map) => {
1740 for (k, v) in map {
1741 let key = if prefix.is_empty() {
1742 k.clone()
1743 } else {
1744 format!("{prefix}.{k}")
1745 };
1746 flatten_config_json(&key, v, out);
1747 }
1748 }
1749 crate::serde_json::Value::String(s) => {
1750 out.push((prefix.to_string(), Value::text(s.clone())));
1751 }
1752 crate::serde_json::Value::Integer(n) => {
1753 out.push((prefix.to_string(), Value::Integer(*n)));
1754 }
1755 crate::serde_json::Value::Number(n) => {
1756 if n.fract().abs() < f64::EPSILON {
1757 out.push((prefix.to_string(), Value::UnsignedInteger(*n as u64)));
1758 } else {
1759 out.push((prefix.to_string(), Value::Float(*n)));
1760 }
1761 }
1762 crate::serde_json::Value::Decimal(n) => {
1763 out.push((prefix.to_string(), Value::DecimalText(n.clone())));
1764 }
1765 crate::serde_json::Value::Bool(b) => {
1766 out.push((prefix.to_string(), Value::Boolean(*b)));
1767 }
1768 crate::serde_json::Value::Null => {
1769 out.push((prefix.to_string(), Value::Null));
1770 }
1771 crate::serde_json::Value::Array(arr) => {
1772 let json_str = crate::serde_json::to_string(value).unwrap_or_default();
1773 out.push((prefix.to_string(), Value::text(json_str)));
1774 }
1775 }
1776}