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