1use super::*;
2use crate::storage::unified::entity_cache::EntityCache;
3use parking_lot::RwLock;
4
5const META_PAGE_CONTENT_CAP: usize =
24 crate::storage::engine::PAGE_SIZE - crate::storage::engine::HEADER_SIZE;
25const META_V3_PAGE1_HEADER: usize = reddb_file::METADATA_OVERFLOW_HEADER_BYTES;
26const META_V3_OVERFLOW_HEADER: usize = reddb_file::METADATA_OVERFLOW_CONTINUATION_HEADER_BYTES;
27const META_V3_FIRST_PAYLOAD_CAP: usize = META_PAGE_CONTENT_CAP - META_V3_PAGE1_HEADER;
28const META_V3_OVERFLOW_PAYLOAD_CAP: usize = META_PAGE_CONTENT_CAP - META_V3_OVERFLOW_HEADER;
29
30fn tree_matches_records(tree: &BTree, records: &[(Vec<u8>, Vec<u8>)]) -> Result<bool, StoreError> {
31 let mut cursor = tree
32 .cursor_first()
33 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
34 for record in records {
35 if cursor
36 .next()
37 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
38 .as_ref()
39 != Some(record)
40 {
41 return Ok(false);
42 }
43 }
44 Ok(cursor
45 .next()
46 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
47 .is_none())
48}
49
50fn existing_overflow_chain(pager: &Pager) -> Result<Vec<u32>, PagerError> {
51 let cs = crate::storage::engine::HEADER_SIZE;
52 let page = match pager.read_page(1) {
53 Ok(p) => p,
54 Err(_) => return Ok(Vec::new()),
55 };
56 let bytes = page.as_bytes();
57 if bytes.len() < cs + META_V3_PAGE1_HEADER {
58 return Ok(Vec::new());
59 }
60 let Some(header) =
61 reddb_file::decode_native_metadata_overflow_header(&bytes[cs..]).map_err(|err| {
62 PagerError::InvalidDatabase(format!("invalid metadata overflow header: {err}"))
63 })?
64 else {
65 return Ok(Vec::new());
66 };
67 let mut next = header.next_overflow_page_id;
68 let mut pages = Vec::new();
69 let mut seen = std::collections::HashSet::new();
70 while next != 0 {
71 if !seen.insert(next) || pages.len() >= pager.page_count()? as usize {
72 return Err(PagerError::InvalidDatabase(
73 "cyclic metadata overflow chain".into(),
74 ));
75 }
76 let ov = match pager.read_page(next) {
77 Ok(p) => p,
78 Err(_) => break,
79 };
80 let ob = ov.as_bytes();
81 let nn = match reddb_file::decode_native_metadata_overflow_continuation_header(&ob[cs..]) {
82 Ok(header) => header.next_overflow_page_id,
83 Err(_) => 0,
84 };
85 pages.push(next);
86 next = nn;
87 }
88 Ok(pages)
89}
90
91fn publish_paged_metadata(pager: &Pager, meta_data: &[u8]) -> Result<(), PagerError> {
92 let retired = existing_overflow_chain(pager)?;
93 let page = build_meta_page1_with_overflow(pager, meta_data)?;
94 pager.sync()?;
96 pager.write_page(1, page)?;
97 pager.sync()?;
98 for id in retired {
99 pager.free_page(id)?;
100 }
101 Ok(())
102}
103
104fn build_meta_page1_with_overflow(
105 pager: &Pager,
106 meta_data: &[u8],
107) -> Result<crate::storage::engine::Page, PagerError> {
108 use crate::storage::engine::{Page, PageType, HEADER_SIZE};
109
110 let mut page1 = Page::new(PageType::Header, 1);
111 let cs = HEADER_SIZE;
112
113 if meta_data.len() <= META_PAGE_CONTENT_CAP {
114 let buf = page1.as_bytes_mut();
116 buf[cs..cs + meta_data.len()].copy_from_slice(meta_data);
117 return Ok(page1);
118 }
119
120 let first_chunk = &meta_data[..META_V3_FIRST_PAYLOAD_CAP];
124 let mut tail = &meta_data[META_V3_FIRST_PAYLOAD_CAP..];
125 let mut chunks: Vec<&[u8]> = Vec::new();
126 while !tail.is_empty() {
127 let take = tail.len().min(META_V3_OVERFLOW_PAYLOAD_CAP);
128 chunks.push(&tail[..take]);
129 tail = &tail[take..];
130 }
131
132 let mut overflow_pages: Vec<Page> = Vec::with_capacity(chunks.len());
133 let mut overflow_ids: Vec<u32> = Vec::with_capacity(chunks.len());
134 for _ in 0..chunks.len() {
135 let pg = pager.allocate_page(PageType::Overflow)?;
136 overflow_ids.push(pg.page_id());
137 overflow_pages.push(pg);
138 }
139
140 for i in 0..chunks.len() {
141 let next = if i + 1 < chunks.len() {
142 overflow_ids[i + 1]
143 } else {
144 0u32
145 };
146 let len = chunks[i].len() as u32;
147 let buf = overflow_pages[i].as_bytes_mut();
148 reddb_file::encode_native_metadata_overflow_continuation_header(
149 &mut buf[cs..cs + META_V3_OVERFLOW_HEADER],
150 reddb_file::NativeMetadataOverflowContinuationHeader {
151 next_overflow_page_id: next,
152 chunk_bytes: len,
153 },
154 )
155 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
156 buf[cs + 8..cs + 8 + chunks[i].len()].copy_from_slice(chunks[i]);
157 }
158 for (idx, page) in overflow_pages.into_iter().enumerate() {
159 let id = overflow_ids[idx];
160 pager.write_page(id, page)?;
161 }
162
163 let format_version = reddb_file::decode_native_paged_metadata_header(meta_data)
165 .ok()
166 .flatten()
167 .map_or(0, |header| header.format_version);
168
169 let buf = page1.as_bytes_mut();
170 reddb_file::encode_native_metadata_overflow_header(
171 &mut buf[cs..cs + META_V3_PAGE1_HEADER],
172 reddb_file::NativeMetadataOverflowHeader {
173 format_version,
174 total_payload_bytes: meta_data.len() as u32,
175 next_overflow_page_id: overflow_ids[0],
176 },
177 )
178 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
179 buf[cs + META_V3_PAGE1_HEADER..cs + META_V3_PAGE1_HEADER + first_chunk.len()]
180 .copy_from_slice(first_chunk);
181
182 Ok(page1)
183}
184
185fn storage_integrity(
186 zone: &str,
187 id: impl Into<String>,
188 collection: Option<String>,
189 detail: impl Into<String>,
190) -> StoreError {
191 StoreError::StorageIntegrity(crate::api::StorageIntegrityError::new(
192 zone, id, collection, detail,
193 ))
194}
195
196fn page_integrity(
197 page_id: u32,
198 collection: Option<String>,
199 detail: impl Into<String>,
200) -> StoreError {
201 storage_integrity("page", page_id.to_string(), collection, detail)
202}
203
204fn read_meta_payload(pager: &Pager) -> Result<Option<Vec<u8>>, StoreError> {
216 let cs = crate::storage::engine::HEADER_SIZE;
217 let meta_page = pager.read_manifest_page().map_err(|err| {
218 storage_integrity(
219 "manifest",
220 "manifest",
221 None,
222 format!("internal manifest zone read failed ({err})"),
223 )
224 })?;
225 let bytes = meta_page.as_bytes();
226 if bytes.len() < cs + 4 {
227 return Ok(Some(bytes.get(cs..).unwrap_or(&[]).to_vec()));
228 }
229 let header = match reddb_file::decode_native_metadata_overflow_header(&bytes[cs..])
230 .map_err(|err| page_integrity(1, None, err.to_string()))?
231 {
232 Some(header) => header,
233 None => {
234 return Ok(Some(bytes[cs..].to_vec()));
235 }
236 };
237 if bytes.len() < cs + META_V3_PAGE1_HEADER {
238 return Err(page_integrity(
239 1,
240 None,
241 "metadata overflow header is truncated",
242 ));
243 }
244 let total = header.total_payload_bytes as usize;
245 let mut next = header.next_overflow_page_id;
246 let mut payload: Vec<u8> = Vec::with_capacity(total);
247 let first_take = total.min(META_V3_FIRST_PAYLOAD_CAP);
248 payload.extend_from_slice(
249 &bytes[cs + META_V3_PAGE1_HEADER..cs + META_V3_PAGE1_HEADER + first_take],
250 );
251 while next != 0 && payload.len() < total {
252 let ov = pager
253 .read_page(next)
254 .map_err(|err| page_integrity(next, None, err.to_string()))?;
255 let ob = ov.as_bytes();
256 if ob.len() < cs + META_V3_OVERFLOW_HEADER {
257 return Err(page_integrity(
258 next,
259 None,
260 "metadata overflow continuation is truncated",
261 ));
262 }
263 let continuation =
264 reddb_file::decode_native_metadata_overflow_continuation_header(&ob[cs..])
265 .map_err(|err| page_integrity(next, None, err.to_string()))?;
266 let nn = continuation.next_overflow_page_id;
267 let len = continuation.chunk_bytes as usize;
268 let remaining = total - payload.len();
269 let take = len.min(remaining).min(META_V3_OVERFLOW_PAYLOAD_CAP);
270 payload.extend_from_slice(
271 &ob[cs + META_V3_OVERFLOW_HEADER..cs + META_V3_OVERFLOW_HEADER + take],
272 );
273 next = nn;
274 }
275 Ok(Some(payload))
276}
277
278impl UnifiedStore {
279 pub(crate) fn mark_paged_registry_dirty(&self) {
280 self.paged_registry_dirty.store(true, Ordering::Release);
281 }
282
283 pub(crate) fn get_or_create_btree(&self, collection: &str) -> Option<Arc<BTree>> {
289 let pager = self.pager.as_ref()?;
290 if let Some(btree) = self.btree_indices.read().get(collection).cloned() {
291 return Some(btree);
292 }
293 let mut write = self.btree_indices.write();
294 let btree = write
295 .entry(collection.to_string())
296 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))))
297 .clone();
298 Some(btree)
299 }
300
301 pub(crate) fn flush_paged_state(&self) -> Result<(), StoreError> {
302 let Some(pager) = &self.pager else {
303 return Ok(());
304 };
305
306 if self.paged_registry_dirty.load(Ordering::Acquire) {
307 self.flush_paged_registry()?;
308 self.paged_registry_dirty.store(false, Ordering::Release);
309 return Ok(());
310 }
311
312 pager
313 .flush()
314 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))
315 }
316
317 pub(crate) fn flush_paged_registry(&self) -> Result<(), StoreError> {
318 let Some(pager) = &self.pager else {
319 return Ok(());
320 };
321
322 match pager.read_page(1) {
323 Ok(_) => {}
324 Err(PagerError::PageNotFound(_)) => {
325 let meta_page = pager
326 .allocate_page(crate::storage::engine::PageType::Header)
327 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
328 pager
329 .write_page(meta_page.page_id(), meta_page)
330 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
331 }
332 Err(e) => {
333 return Err(StoreError::Io(std::io::Error::other(e.to_string())));
334 }
335 }
336
337 let format_version = STORE_VERSION_V11;
338 self.set_format_version(format_version);
339
340 let collections = self.collections.read();
341 let btree_indices = self.btree_indices.read();
342 let mut collection_roots = Vec::with_capacity(collections.len());
343 for (name, _) in collections.iter() {
344 let root_page = btree_indices
345 .get(name)
346 .map_or(0, |btree| btree.root_page_id());
347 collection_roots.push((name.clone(), root_page));
348 }
349 drop(btree_indices);
350 drop(collections);
351
352 let mut meta_data = Vec::with_capacity(4096);
353 reddb_file::encode_native_paged_metadata_header(
354 &mut meta_data,
355 reddb_file::NativePagedMetadataHeader {
356 format_version,
357 collection_count: collection_roots.len() as u32,
358 },
359 );
360 for (name, root_page) in &collection_roots {
361 reddb_file::encode_native_paged_collection_root(&mut meta_data, name, *root_page);
362 }
363
364 let cross_refs = self.cross_refs.read();
365 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
366 meta_data.extend_from_slice(&(total_refs as u32).to_le_bytes());
367 for (source_id, refs) in cross_refs.iter() {
368 for (target_id, ref_type, collection) in refs {
369 reddb_file::encode_native_paged_cross_ref(
370 &mut meta_data,
371 source_id.raw(),
372 target_id.raw(),
373 ref_type.to_byte(),
374 collection,
375 );
376 }
377 }
378
379 publish_paged_metadata(pager, &meta_data)
380 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
381
382 Ok(())
383 }
384
385 pub fn pager(&self) -> Option<&Arc<Pager>> {
387 self.pager.as_ref()
388 }
389
390 pub fn page_cache_bytes_in_use(&self) -> u64 {
394 self.pager.as_ref().map_or(0, |pager| {
395 pager.cache_len() as u64 * crate::storage::memory_pools::PAGE_CACHE_PAGE_SIZE_BYTES
396 })
397 }
398
399 pub fn wal_buffer_bytes_in_use(&self) -> u64 {
402 self.commit
403 .as_ref()
404 .map_or(0, |commit| commit.buffered_bytes())
405 }
406
407 pub fn segment_memory_bytes(&self) -> u64 {
411 let managers: Vec<Arc<SegmentManager>> =
412 self.collections.read().values().map(Arc::clone).collect();
413
414 managers
415 .iter()
416 .map(|manager| manager.resident_bytes())
417 .fold(0, u64::saturating_add)
418 }
419
420 pub fn config(&self) -> &UnifiedStoreConfig {
424 &self.config
425 }
426
427 pub fn with_config(config: UnifiedStoreConfig) -> Self {
428 Self {
429 config,
430 format_version: AtomicU32::new(STORE_VERSION_V11),
431 next_entity_id: AtomicU64::new(1),
432 collections: RwLock::new(HashMap::new()),
433 cross_refs: RwLock::new(HashMap::new()),
434 reverse_refs: RwLock::new(HashMap::new()),
435 pager: None,
436 db_path: None,
437 btree_indices: RwLock::new(HashMap::new()),
438 context_index: ContextIndex::new(),
439 entity_cache: EntityCache::new(),
440 graph_label_index: RwLock::new(HashMap::new()),
441 paged_registry_dirty: AtomicBool::new(false),
442 commit: None,
443 unindex_cross_refs_fast_path: AtomicU64::new(0),
444 replayed_turbo_inserts: parking_lot::Mutex::new(HashMap::new()),
445 replayed_probabilistic_deltas: parking_lot::Mutex::new(Vec::new()),
446 aux_metadata: RwLock::new(Vec::new()),
447 snapshot_manager: std::sync::OnceLock::new(),
448 vault_publication_lock: parking_lot::ReentrantMutex::new(()),
449 pending_vault_versions: RwLock::new(std::collections::HashSet::new()),
450 }
451 }
452
453 pub fn open(path: impl AsRef<Path>) -> Result<Self, StoreError> {
471 Self::open_with_config(path, UnifiedStoreConfig::default())
472 }
473
474 pub fn open_with_config(
475 path: impl AsRef<Path>,
476 config: UnifiedStoreConfig,
477 ) -> Result<Self, StoreError> {
478 let path = path.as_ref();
479 let mut pager_config = PagerConfig::default();
480 if let Some(slots) = config.page_cache_slots {
484 pager_config.cache_size = slots;
485 }
486 if matches!(
494 std::env::var("REDDB_DOUBLE_WRITE").ok().as_deref(),
495 Some("0") | Some("false") | Some("off")
496 ) {
497 pager_config.double_write = false;
498 }
499 let pager = Pager::open(path, pager_config)
500 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
501
502 let wal_path = reddb_file::layout::unified_wal_path(path);
503 let commit = Some(Arc::new(
506 StoreCommitCoordinator::open(wal_path, config.durability_mode, config.group_commit)
507 .map_err(StoreError::Io)?,
508 ));
509
510 let store = Self {
511 config,
512 format_version: AtomicU32::new(STORE_VERSION_V11),
513 next_entity_id: AtomicU64::new(1),
514 collections: RwLock::new(HashMap::new()),
515 cross_refs: RwLock::new(HashMap::new()),
516 reverse_refs: RwLock::new(HashMap::new()),
517 pager: Some(Arc::new(pager)),
518 db_path: Some(path.to_path_buf()),
519 btree_indices: RwLock::new(HashMap::new()),
520 context_index: ContextIndex::new(),
521 entity_cache: EntityCache::new(),
522 graph_label_index: RwLock::new(HashMap::new()),
523 paged_registry_dirty: AtomicBool::new(false),
524 commit,
525 unindex_cross_refs_fast_path: AtomicU64::new(0),
526 replayed_turbo_inserts: parking_lot::Mutex::new(HashMap::new()),
527 replayed_probabilistic_deltas: parking_lot::Mutex::new(Vec::new()),
528 aux_metadata: RwLock::new(Vec::new()),
529 snapshot_manager: std::sync::OnceLock::new(),
530 vault_publication_lock: parking_lot::ReentrantMutex::new(()),
531 pending_vault_versions: RwLock::new(std::collections::HashSet::new()),
532 };
533
534 store.load_from_pages()?;
536 if let Some(commit) = &store.commit {
537 commit.replay_into(&store).map_err(StoreError::Io)?;
538 }
539 store.recover_operational_manifest()?;
540
541 Ok(store)
542 }
543
544 pub(crate) fn reconcile_replayed_btrees(&self) -> Result<(), StoreError> {
548 let Some(pager) = &self.pager else {
549 return Ok(());
550 };
551 let collections = self.collections.read();
552 let mut indices = self.btree_indices.write();
553 for (name, manager) in collections.iter() {
554 let mut records: Vec<_> = manager
555 .query_all(|_| true)
556 .into_iter()
557 .map(|entity| {
558 let metadata = manager.get_metadata(entity.id);
559 (
560 entity.id.raw().to_be_bytes().to_vec(),
561 Self::serialize_entity_record(
562 &entity,
563 metadata.as_ref(),
564 self.format_version(),
565 ),
566 )
567 })
568 .collect();
569 records.sort_by(|left, right| left.0.cmp(&right.0));
570 let matches = if let Some(tree) = indices.get(name) {
571 tree_matches_records(tree, &records)?
572 } else {
573 records.is_empty()
574 };
575 if matches {
576 continue;
577 }
578 let tree = Arc::new(BTree::new(Arc::clone(pager)));
579 tree.bulk_insert_sorted(&records).map_err(|e| {
580 StoreError::Io(std::io::Error::other(format!(
581 "WAL tree rebuild for '{name}': {e}"
582 )))
583 })?;
584 indices.insert(name.clone(), tree);
585 self.mark_paged_registry_dirty();
586 }
587 drop(indices);
588 drop(collections);
589 self.flush_paged_state()
590 }
591
592 pub(crate) fn recover_operational_manifest(&self) -> Result<(), StoreError> {
593 let Some(path) = &self.db_path else {
594 return Ok(());
595 };
596 let mut collections = self.list_collections();
597 collections.sort();
598 let pending_drops =
599 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
600 .recover_or_bootstrap(&collections)
601 .map_err(|err| storage_integrity("manifest", "current", None, err.to_string()))?;
602 for name in pending_drops {
603 if self.get_collection(&name).is_some() {
604 self.drop_collection(&name)?;
605 }
606 }
607 Ok(())
608 }
609
610 pub(crate) fn prepare_operational_collection_create(
611 &self,
612 name: &str,
613 ) -> Result<Option<reddb_file::PreparedCollectionCreate<reddb_file::StdVfs>>, StoreError> {
614 let Some(path) = &self.db_path else {
615 return Ok(None);
616 };
617 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
618 .prepare_collection_create(name)
619 .map(Some)
620 .map_err(StoreError::Io)
621 }
622
623 pub(crate) fn prepare_operational_collection_drop(
624 &self,
625 name: &str,
626 ) -> Result<Option<reddb_file::PreparedCollectionDrop<reddb_file::StdVfs>>, StoreError> {
627 let Some(path) = &self.db_path else {
628 return Ok(None);
629 };
630 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
631 .prepare_collection_drop(name)
632 .map(Some)
633 .map_err(StoreError::Io)
634 }
635
636 fn load_from_pages(&self) -> Result<(), StoreError> {
640 let pager = match &self.pager {
641 Some(p) => p,
642 None => return Ok(()), };
644
645 let page_count = pager.page_count().map_err(|e| {
647 StoreError::Io(std::io::Error::other(format!(
648 "failed to read page count: {}",
649 e
650 )))
651 })?;
652 if page_count <= 1 {
653 return Ok(());
655 }
656
657 if let Some(content_vec) = read_meta_payload(pager)? {
662 let content: &[u8] = &content_vec;
663 if content.len() >= 4 {
664 let mut pos = 0;
665 let mut format_version = STORE_VERSION_V1;
666
667 let collection_count = if let Some(header) =
668 reddb_file::decode_native_paged_metadata_header(content)
669 .map_err(|err| StoreError::Serialization(err.to_string()))?
670 {
671 format_version = header.format_version;
672 pos += reddb_file::METADATA_HEADER_BYTES;
673 header.collection_count as usize
674 } else {
675 let count = u32::from_le_bytes([
676 content[pos],
677 content[pos + 1],
678 content[pos + 2],
679 content[pos + 3],
680 ]) as usize;
681 pos += 4;
682 count
683 };
684
685 self.set_format_version(format_version);
686
687 if pos > content.len() {
688 return Ok(());
689 }
690
691 for _ in 0..collection_count {
693 if let Ok(root) =
694 reddb_file::decode_native_paged_collection_root(content, &mut pos)
695 {
696 let root_page = root.root_page;
698 let name = root.collection;
699
700 let _ = self.create_collection_in_memory(&name);
704
705 if root_page > 0 {
707 let btree = BTree::with_root(Arc::clone(pager), root_page);
708
709 let mut cursor = btree.cursor_first().map_err(|err| {
711 page_integrity(root_page, Some(name.clone()), err.to_string())
712 })?;
713 let manager = self.get_collection(&name);
714 loop {
715 let next = cursor.next().map_err(|err| {
716 page_integrity(root_page, Some(name.clone()), err.to_string())
717 })?;
718 let Some((key, value)) = next else {
719 break;
720 };
721 if let Ok((entity, metadata)) =
723 Self::deserialize_entity_record(&value, self.format_version())
724 {
725 if let Some(m) = &manager {
726 let id = entity.id;
727 if let EntityKind::TableRow { row_id, .. } = &entity.kind {
728 m.register_row_id(*row_id);
729 }
730 self.context_index.index_entity(&name, &entity);
731 let _ = m.insert(entity.clone());
732 if let Some(metadata) = metadata {
733 let _ = m.set_metadata(id, metadata);
734 }
735 self.register_entity_id(id);
736 if self.config.auto_index_refs {
737 self.index_cross_refs(&entity, &name)?;
738 }
739 }
740 }
741 }
742
743 self.btree_indices.write().insert(name, Arc::new(btree));
745 }
746 } else {
747 break;
748 }
749 }
750
751 if format_version >= STORE_VERSION_V2 && pos + 4 <= content.len() {
752 let cross_ref_count = u32::from_le_bytes([
753 content[pos],
754 content[pos + 1],
755 content[pos + 2],
756 content[pos + 3],
757 ]) as usize;
758 pos += 4;
759
760 for _ in 0..cross_ref_count {
761 let Ok(cross_ref) =
762 reddb_file::decode_native_paged_cross_ref(content, &mut pos)
763 else {
764 break;
765 };
766 let source_id = EntityId::new(cross_ref.source_id);
767 let target_id = EntityId::new(cross_ref.target_id);
768 let ref_type = RefType::from_byte(cross_ref.ref_type);
769 let target_collection = cross_ref.target_collection;
770
771 self.cross_refs.write().entry(source_id).or_default().push((
772 target_id,
773 ref_type,
774 target_collection.clone(),
775 ));
776
777 if let Some((collection, mut entity)) = self.get_any(source_id) {
778 let exists = entity.cross_refs().iter().any(|xref| {
779 xref.target == target_id
780 && xref.ref_type == ref_type
781 && xref.target_collection == target_collection
782 });
783 if !exists {
784 entity.cross_refs_mut().push(CrossRef::new(
785 source_id,
786 target_id,
787 target_collection.clone(),
788 ref_type,
789 ));
790 if let Some(manager) = self.get_collection(&collection) {
791 let _ = manager.update(entity);
792 }
793 }
794 }
795 }
796 }
797 }
798 }
799
800 if self.format_version() < STORE_VERSION_V11 {
801 self.set_format_version(STORE_VERSION_V11);
802 }
803
804 Ok(())
805 }
806
807 pub(crate) fn deserialize_entity(
809 data: &[u8],
810 format_version: u32,
811 ) -> Result<UnifiedEntity, StoreError> {
812 let mut pos = 0;
813 Self::read_entity_binary(data, &mut pos, format_version)
814 .map_err(|e| StoreError::Serialization(e.to_string()))
815 }
816
817 pub(crate) fn serialize_entity(entity: &UnifiedEntity, format_version: u32) -> Vec<u8> {
819 let mut buf = Vec::with_capacity(256);
824 Self::write_entity_binary(&mut buf, entity, format_version);
825 buf
826 }
827
828 pub(crate) fn serialize_entity_record(
829 entity: &UnifiedEntity,
830 metadata: Option<&Metadata>,
831 format_version: u32,
832 ) -> Vec<u8> {
833 let entity_bytes = Self::serialize_entity(entity, format_version);
834 let has_meta = matches!(metadata, Some(m) if !m.fields.is_empty());
840 if has_meta {
841 let metadata_bytes = serialize_metadata(metadata);
842 reddb_file::encode_native_entity_record_frame(&entity_bytes, Some(&metadata_bytes))
843 } else {
844 reddb_file::encode_native_entity_record_frame(&entity_bytes, None)
845 }
846 }
847
848 pub(crate) fn deserialize_entity_record(
849 data: &[u8],
850 format_version: u32,
851 ) -> Result<(UnifiedEntity, Option<Metadata>), StoreError> {
852 let Some(frame) = reddb_file::decode_native_entity_record_frame(data)
853 .map_err(|err| StoreError::Serialization(err.to_string()))?
854 else {
855 return Self::deserialize_entity(data, format_version).map(|entity| (entity, None));
856 };
857
858 let entity = Self::deserialize_entity(frame.entity, format_version)?;
859 let metadata = if frame.metadata.is_empty() {
860 None
861 } else {
862 let metadata = deserialize_metadata(frame.metadata)?;
863 if metadata.is_empty() {
864 None
865 } else {
866 Some(metadata)
867 }
868 };
869
870 Ok((entity, metadata))
871 }
872
873 pub fn persist(&self) -> Result<(), StoreError> {
878 let _vault_guard = self.vault_publication_lock.lock();
879 let pending_vault_versions = self.pending_vault_versions.read();
880 let pager = match &self.pager {
881 Some(p) => p,
882 None => {
883 if let Some(path) = &self.db_path {
885 return self
886 .save_to_file(path)
887 .map_err(|e| StoreError::Serialization(e.to_string()));
888 }
889 return Err(StoreError::Io(std::io::Error::other(
890 "No pager or path configured for persistence",
891 )));
892 }
893 };
894
895 match pager.read_page(1) {
896 Ok(_) => {}
897 Err(PagerError::PageNotFound(_)) => {
898 let meta_page = pager
899 .allocate_page(crate::storage::engine::PageType::Header)
900 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
901 pager
902 .write_page(meta_page.page_id(), meta_page)
903 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
904 }
905 Err(e) => {
906 return Err(StoreError::Io(std::io::Error::other(e.to_string())));
907 }
908 }
909
910 if let Some(commit) = &self.commit {
911 commit.force_sync().map_err(StoreError::Io)?;
912 }
913
914 let collections = self.collections.read();
915 let mut btree_indices = self.btree_indices.write();
916
917 let mut collection_roots: Vec<(String, u32)> = Vec::new();
919
920 let mut rebuilt_indices = HashMap::with_capacity(collections.len());
923 for (name, manager) in collections.iter() {
924 let mut records = Vec::new();
925 manager.for_each_entity_with_metadata(|entity, metadata| {
926 if pending_vault_versions.contains(&entity.id)
927 || (matches!(entity.data, EntityData::Row(_) | EntityData::Vector(_))
928 && self
929 .snapshot_manager
930 .get()
931 .is_some_and(|m| m.is_aborted(entity.xmin)))
932 {
933 return true;
934 }
935 let mut normalized = std::borrow::Cow::Borrowed(entity);
938 if matches!(entity.data, EntityData::Row(_))
939 && self
940 .snapshot_manager
941 .get()
942 .is_some_and(|m| m.is_aborted(entity.xmax))
943 {
944 normalized.to_mut().xmax = 0;
945 }
946 records.push((
947 entity.id.raw().to_be_bytes().to_vec(),
948 Self::serialize_entity_record(
949 &normalized,
950 metadata.as_ref(),
951 self.format_version(),
952 ),
953 ));
954 true
955 });
956 records.sort_by(|left, right| left.0.cmp(&right.0));
957
958 if let Some(tree) = btree_indices.get(name) {
959 if tree_matches_records(tree, &records)? {
960 collection_roots.push((name.clone(), tree.root_page_id()));
961 rebuilt_indices.insert(name.clone(), Arc::clone(tree));
962 continue;
963 }
964 }
965
966 let btree = Arc::new(BTree::new(Arc::clone(pager)));
967
968 if !records.is_empty() {
976 btree.bulk_insert_sorted(&records).map_err(|e| {
977 StoreError::Io(std::io::Error::other(format!(
978 "B-tree bulk rebuild error for '{name}': {e}"
979 )))
980 })?;
981 }
982
983 collection_roots.push((name.clone(), btree.root_page_id()));
984 rebuilt_indices.insert(name.clone(), btree);
985 }
986
987 let mut meta_data = Vec::with_capacity(4096);
989
990 let format_version = STORE_VERSION_V11;
991 self.set_format_version(format_version);
992
993 reddb_file::encode_native_paged_metadata_header(
994 &mut meta_data,
995 reddb_file::NativePagedMetadataHeader {
996 format_version,
997 collection_count: collection_roots.len() as u32,
998 },
999 );
1000
1001 for (name, root_page) in &collection_roots {
1003 reddb_file::encode_native_paged_collection_root(&mut meta_data, name, *root_page);
1004 }
1005
1006 let cross_refs = self.cross_refs.read();
1008 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
1009 meta_data.extend_from_slice(&(total_refs as u32).to_le_bytes());
1010 for (source_id, refs) in cross_refs.iter() {
1011 for (target_id, ref_type, collection) in refs {
1012 reddb_file::encode_native_paged_cross_ref(
1013 &mut meta_data,
1014 source_id.raw(),
1015 target_id.raw(),
1016 ref_type.to_byte(),
1017 collection,
1018 );
1019 }
1020 }
1021
1022 publish_paged_metadata(pager, &meta_data)
1023 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
1024
1025 *btree_indices = rebuilt_indices;
1028
1029 if let Some(commit) = &self.commit {
1030 commit.truncate().map_err(StoreError::Io)?;
1031 }
1032
1033 Ok(())
1034 }
1035
1036 pub fn is_paged(&self) -> bool {
1038 self.pager.is_some()
1039 }
1040
1041 pub(crate) fn collection_root_page(&self, collection: &str) -> Option<u32> {
1044 self.btree_indices
1045 .read()
1046 .get(collection)
1047 .map(|btree| btree.root_page_id())
1048 .filter(|root| *root != 0)
1049 }
1050
1051 pub fn db_path(&self) -> Option<&Path> {
1053 self.db_path.as_deref()
1054 }
1055}
1056
1057fn serialize_metadata(metadata: Option<&Metadata>) -> Vec<u8> {
1058 let Some(metadata) = metadata else {
1059 return Vec::new();
1060 };
1061 if metadata.is_empty() {
1062 return Vec::new();
1063 }
1064
1065 let mut entries: Vec<_> = metadata.iter().collect();
1066 entries.sort_by_key(|(a, _)| *a);
1067
1068 let mut buf = Vec::new();
1069 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
1070 for (key, value) in entries {
1071 write_string(&mut buf, key);
1072 write_metadata_value(&mut buf, value);
1073 }
1074 buf
1075}
1076
1077fn deserialize_metadata(data: &[u8]) -> Result<Metadata, StoreError> {
1078 let mut pos = 0usize;
1079 let count = read_u32(data, &mut pos)? as usize;
1080 let mut metadata = Metadata::new();
1081 for _ in 0..count {
1082 let key = read_string(data, &mut pos)?;
1083 let value = read_metadata_value(data, &mut pos)?;
1084 metadata.set(key, value);
1085 }
1086 Ok(metadata)
1087}
1088
1089fn write_string(buf: &mut Vec<u8>, value: &str) {
1090 reddb_file::encode_native_len_prefixed_str(buf, value);
1091}
1092
1093fn write_bytes(buf: &mut Vec<u8>, value: &[u8]) {
1094 reddb_file::encode_native_len_prefixed_bytes(buf, value);
1095}
1096
1097fn write_ref_target(buf: &mut Vec<u8>, target: &crate::storage::unified::RefTarget) {
1098 use crate::storage::unified::RefTarget;
1099
1100 match target {
1101 RefTarget::TableRow { table, row_id } => {
1102 buf.push(0);
1103 write_string(buf, table);
1104 buf.extend_from_slice(&row_id.to_le_bytes());
1105 }
1106 RefTarget::Node {
1107 collection,
1108 node_id,
1109 } => {
1110 buf.push(1);
1111 write_string(buf, collection);
1112 buf.extend_from_slice(&node_id.raw().to_le_bytes());
1113 }
1114 RefTarget::Edge {
1115 collection,
1116 edge_id,
1117 } => {
1118 buf.push(2);
1119 write_string(buf, collection);
1120 buf.extend_from_slice(&edge_id.raw().to_le_bytes());
1121 }
1122 RefTarget::Vector {
1123 collection,
1124 vector_id,
1125 } => {
1126 buf.push(3);
1127 write_string(buf, collection);
1128 buf.extend_from_slice(&vector_id.raw().to_le_bytes());
1129 }
1130 RefTarget::Entity {
1131 collection,
1132 entity_id,
1133 } => {
1134 buf.push(4);
1135 write_string(buf, collection);
1136 buf.extend_from_slice(&entity_id.raw().to_le_bytes());
1137 }
1138 }
1139}
1140
1141fn write_metadata_value(buf: &mut Vec<u8>, value: &MetadataValue) {
1142 match value {
1143 MetadataValue::Null => buf.push(0),
1144 MetadataValue::Bool(v) => {
1145 buf.push(1);
1146 buf.push(u8::from(*v));
1147 }
1148 MetadataValue::Int(v) => {
1149 buf.push(2);
1150 buf.extend_from_slice(&v.to_le_bytes());
1151 }
1152 MetadataValue::Float(v) => {
1153 buf.push(3);
1154 buf.extend_from_slice(&v.to_le_bytes());
1155 }
1156 MetadataValue::String(v) => {
1157 buf.push(4);
1158 write_string(buf, v);
1159 }
1160 MetadataValue::Bytes(v) => {
1161 buf.push(5);
1162 write_bytes(buf, v);
1163 }
1164 MetadataValue::Array(values) => {
1165 buf.push(6);
1166 buf.extend_from_slice(&(values.len() as u32).to_le_bytes());
1167 for value in values {
1168 write_metadata_value(buf, value);
1169 }
1170 }
1171 MetadataValue::Object(values) => {
1172 buf.push(7);
1173 let mut entries: Vec<_> = values.iter().collect();
1174 entries.sort_by_key(|(a, _)| *a);
1175 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
1176 for (key, value) in entries {
1177 write_string(buf, key);
1178 write_metadata_value(buf, value);
1179 }
1180 }
1181 MetadataValue::Timestamp(v) => {
1182 buf.push(8);
1183 buf.extend_from_slice(&v.to_le_bytes());
1184 }
1185 MetadataValue::Geo { lat, lon } => {
1186 buf.push(9);
1187 buf.extend_from_slice(&lat.to_le_bytes());
1188 buf.extend_from_slice(&lon.to_le_bytes());
1189 }
1190 MetadataValue::Reference(target) => {
1191 buf.push(10);
1192 write_ref_target(buf, target);
1193 }
1194 MetadataValue::References(targets) => {
1195 buf.push(11);
1196 buf.extend_from_slice(&(targets.len() as u32).to_le_bytes());
1197 for target in targets {
1198 write_ref_target(buf, target);
1199 }
1200 }
1201 }
1202}
1203
1204fn read_exact_slice<'a>(
1205 data: &'a [u8],
1206 pos: &mut usize,
1207 len: usize,
1208) -> Result<&'a [u8], StoreError> {
1209 if *pos + len > data.len() {
1210 return Err(StoreError::Serialization(
1211 "truncated metadata payload".to_string(),
1212 ));
1213 }
1214 let slice = &data[*pos..*pos + len];
1215 *pos += len;
1216 Ok(slice)
1217}
1218
1219fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, StoreError> {
1220 let bytes = read_exact_slice(data, pos, 4)?;
1221 let mut raw = [0u8; 4];
1222 raw.copy_from_slice(bytes);
1223 Ok(u32::from_le_bytes(raw))
1224}
1225
1226fn read_u64(data: &[u8], pos: &mut usize) -> Result<u64, StoreError> {
1227 let bytes = read_exact_slice(data, pos, 8)?;
1228 let mut raw = [0u8; 8];
1229 raw.copy_from_slice(bytes);
1230 Ok(u64::from_le_bytes(raw))
1231}
1232
1233fn read_i64(data: &[u8], pos: &mut usize) -> Result<i64, StoreError> {
1234 let bytes = read_exact_slice(data, pos, 8)?;
1235 let mut raw = [0u8; 8];
1236 raw.copy_from_slice(bytes);
1237 Ok(i64::from_le_bytes(raw))
1238}
1239
1240fn read_f64(data: &[u8], pos: &mut usize) -> Result<f64, StoreError> {
1241 let bytes = read_exact_slice(data, pos, 8)?;
1242 let mut raw = [0u8; 8];
1243 raw.copy_from_slice(bytes);
1244 Ok(f64::from_le_bytes(raw))
1245}
1246
1247fn read_u8(data: &[u8], pos: &mut usize) -> Result<u8, StoreError> {
1248 let bytes = read_exact_slice(data, pos, 1)?;
1249 Ok(bytes[0])
1250}
1251
1252fn read_string(data: &[u8], pos: &mut usize) -> Result<String, StoreError> {
1253 reddb_file::decode_native_len_prefixed_string(data, pos)
1254 .map_err(|err| StoreError::Serialization(err.to_string()))
1255}
1256
1257fn read_bytes(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, StoreError> {
1258 reddb_file::decode_native_len_prefixed_bytes(data, pos)
1259 .map(|bytes| bytes.to_vec())
1260 .map_err(|err| StoreError::Serialization(err.to_string()))
1261}
1262
1263fn read_ref_target(
1264 data: &[u8],
1265 pos: &mut usize,
1266) -> Result<crate::storage::unified::RefTarget, StoreError> {
1267 use crate::storage::unified::RefTarget;
1268
1269 match read_u8(data, pos)? {
1270 0 => Ok(RefTarget::TableRow {
1271 table: read_string(data, pos)?,
1272 row_id: read_u64(data, pos)?,
1273 }),
1274 1 => Ok(RefTarget::Node {
1275 collection: read_string(data, pos)?,
1276 node_id: EntityId::new(read_u64(data, pos)?),
1277 }),
1278 2 => Ok(RefTarget::Edge {
1279 collection: read_string(data, pos)?,
1280 edge_id: EntityId::new(read_u64(data, pos)?),
1281 }),
1282 3 => Ok(RefTarget::Vector {
1283 collection: read_string(data, pos)?,
1284 vector_id: EntityId::new(read_u64(data, pos)?),
1285 }),
1286 4 => Ok(RefTarget::Entity {
1287 collection: read_string(data, pos)?,
1288 entity_id: EntityId::new(read_u64(data, pos)?),
1289 }),
1290 tag => Err(StoreError::Serialization(format!(
1291 "unknown metadata ref target tag {tag}"
1292 ))),
1293 }
1294}
1295
1296fn read_metadata_value(data: &[u8], pos: &mut usize) -> Result<MetadataValue, StoreError> {
1297 match read_u8(data, pos)? {
1298 0 => Ok(MetadataValue::Null),
1299 1 => Ok(MetadataValue::Bool(read_u8(data, pos)? != 0)),
1300 2 => Ok(MetadataValue::Int(read_i64(data, pos)?)),
1301 3 => Ok(MetadataValue::Float(read_f64(data, pos)?)),
1302 4 => Ok(MetadataValue::String(read_string(data, pos)?)),
1303 5 => Ok(MetadataValue::Bytes(read_bytes(data, pos)?)),
1304 6 => {
1305 let count = read_u32(data, pos)? as usize;
1306 let mut values = Vec::with_capacity(count);
1307 for _ in 0..count {
1308 values.push(read_metadata_value(data, pos)?);
1309 }
1310 Ok(MetadataValue::Array(values))
1311 }
1312 7 => {
1313 let count = read_u32(data, pos)? as usize;
1314 let mut values = std::collections::HashMap::with_capacity(count);
1315 for _ in 0..count {
1316 let key = read_string(data, pos)?;
1317 let value = read_metadata_value(data, pos)?;
1318 values.insert(key, value);
1319 }
1320 Ok(MetadataValue::Object(values))
1321 }
1322 8 => Ok(MetadataValue::Timestamp(read_u64(data, pos)?)),
1323 9 => Ok(MetadataValue::Geo {
1324 lat: read_f64(data, pos)?,
1325 lon: read_f64(data, pos)?,
1326 }),
1327 10 => Ok(MetadataValue::Reference(read_ref_target(data, pos)?)),
1328 11 => {
1329 let count = read_u32(data, pos)? as usize;
1330 let mut targets = Vec::with_capacity(count);
1331 for _ in 0..count {
1332 targets.push(read_ref_target(data, pos)?);
1333 }
1334 Ok(MetadataValue::References(targets))
1335 }
1336 tag => Err(StoreError::Serialization(format!(
1337 "unknown metadata value tag {tag}"
1338 ))),
1339 }
1340}