1use super::*;
2use crate::storage::unified::entity_cache::EntityCache;
3use parking_lot::RwLock;
4
5const CHECKPOINT_BATCH_BYTES: usize = 1024 * 1024;
6const CHECKPOINT_BATCH_ROWS: usize = 512;
7
8struct CheckpointTreeBuilder {
11 tree: Arc<BTree>,
12 records: Vec<(Vec<u8>, Vec<u8>)>,
13 bytes: usize,
14}
15
16impl CheckpointTreeBuilder {
17 fn new(pager: &Arc<Pager>) -> Self {
18 Self {
19 tree: Arc::new(BTree::new(Arc::clone(pager))),
20 records: Vec::new(),
21 bytes: 0,
22 }
23 }
24
25 fn push(&mut self, record: (Vec<u8>, Vec<u8>)) -> Result<(), StoreError> {
26 let bytes = record.0.len() + record.1.len();
27 if !self.records.is_empty()
28 && (self.bytes.saturating_add(bytes) > CHECKPOINT_BATCH_BYTES
29 || self.records.len() >= CHECKPOINT_BATCH_ROWS)
30 {
31 self.flush()?;
32 }
33 self.bytes += bytes;
34 self.records.push(record);
35 #[cfg(test)]
36 CHECKPOINT_BUFFERED_BYTES.with(|peak| peak.set(peak.get().max(self.bytes)));
37 Ok(())
38 }
39
40 fn flush(&mut self) -> Result<(), StoreError> {
41 self.tree
42 .bulk_insert_sorted(&self.records)
43 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
44 self.records.clear();
45 self.bytes = 0;
46 Ok(())
47 }
48
49 fn copy_prefix(&mut self, previous: &BTree, count: usize) -> Result<(), StoreError> {
50 let mut cursor = previous
51 .cursor_first()
52 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
53 for _ in 0..count {
54 let record = cursor
55 .next()
56 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
57 .ok_or_else(|| StoreError::Serialization("checkpoint prefix disappeared".into()))?;
58 self.push(record)?;
59 }
60 Ok(())
61 }
62}
63
64#[cfg(test)]
65thread_local! {
66 static CHECKPOINT_SERIALIZED_ROWS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
67 static CHECKPOINT_BUFFERED_BYTES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
68}
69
70fn checkpoint_visibility_matches(
71 previous: &CheckpointVersion,
72 current: &CheckpointVersion,
73 manager: &SegmentManager,
74) -> bool {
75 if previous.aborted_revision == current.aborted_revision
76 && previous.pending_vault == current.pending_vault
77 {
78 return true;
79 }
80 let mut matches = true;
83 manager.for_each_entity(|entity| {
84 let skipped = |version: &CheckpointVersion| {
85 version.pending_vault.contains(&entity.id)
86 || (matches!(entity.data, EntityData::Row(_) | EntityData::Vector(_))
87 && version.aborted.contains(&entity.xmin))
88 };
89 let previous_skipped = skipped(previous);
90 let current_skipped = skipped(current);
91 matches = previous_skipped == current_skipped
92 && (current_skipped
93 || !matches!(entity.data, EntityData::Row(_))
94 || previous.aborted.contains(&entity.xmax)
95 == current.aborted.contains(&entity.xmax));
96 matches
97 });
98 matches
99}
100
101const META_PAGE_CONTENT_CAP: usize =
120 crate::storage::engine::PAGE_SIZE - crate::storage::engine::HEADER_SIZE;
121const META_V3_PAGE1_HEADER: usize = reddb_file::METADATA_OVERFLOW_HEADER_BYTES;
122const META_V3_OVERFLOW_HEADER: usize = reddb_file::METADATA_OVERFLOW_CONTINUATION_HEADER_BYTES;
123const META_V3_FIRST_PAYLOAD_CAP: usize = META_PAGE_CONTENT_CAP - META_V3_PAGE1_HEADER;
124const META_V3_OVERFLOW_PAYLOAD_CAP: usize = META_PAGE_CONTENT_CAP - META_V3_OVERFLOW_HEADER;
125
126fn existing_overflow_chain(pager: &Pager) -> Result<Vec<u32>, PagerError> {
127 let cs = crate::storage::engine::HEADER_SIZE;
128 let page = match pager.read_page(1) {
129 Ok(p) => p,
130 Err(_) => return Ok(Vec::new()),
131 };
132 let bytes = page.as_bytes();
133 if bytes.len() < cs + META_V3_PAGE1_HEADER {
134 return Ok(Vec::new());
135 }
136 let Some(header) =
137 reddb_file::decode_native_metadata_overflow_header(&bytes[cs..]).map_err(|err| {
138 PagerError::InvalidDatabase(format!("invalid metadata overflow header: {err}"))
139 })?
140 else {
141 return Ok(Vec::new());
142 };
143 let mut next = header.next_overflow_page_id;
144 let mut pages = Vec::new();
145 let mut seen = std::collections::HashSet::new();
146 while next != 0 {
147 if !seen.insert(next) || pages.len() >= pager.page_count()? as usize {
148 return Err(PagerError::InvalidDatabase(
149 "cyclic metadata overflow chain".into(),
150 ));
151 }
152 let ov = match pager.read_page(next) {
153 Ok(p) => p,
154 Err(_) => break,
155 };
156 let ob = ov.as_bytes();
157 let nn = match reddb_file::decode_native_metadata_overflow_continuation_header(&ob[cs..]) {
158 Ok(header) => header.next_overflow_page_id,
159 Err(_) => 0,
160 };
161 pages.push(next);
162 next = nn;
163 }
164 Ok(pages)
165}
166
167fn publish_paged_metadata(pager: &Pager, meta_data: &[u8]) -> Result<(), PagerError> {
168 let retired = existing_overflow_chain(pager)?;
169 let page = build_meta_page1_with_overflow(pager, meta_data)?;
170 pager.sync()?;
172 pager.write_page(1, page)?;
173 pager.sync()?;
174 for id in retired {
175 pager.free_page(id)?;
176 }
177 Ok(())
178}
179
180fn build_meta_page1_with_overflow(
181 pager: &Pager,
182 meta_data: &[u8],
183) -> Result<crate::storage::engine::Page, PagerError> {
184 use crate::storage::engine::{Page, PageType, HEADER_SIZE};
185
186 let mut page1 = Page::new(PageType::Header, 1);
187 let cs = HEADER_SIZE;
188
189 if meta_data.len() <= META_PAGE_CONTENT_CAP {
190 let buf = page1.as_bytes_mut();
192 buf[cs..cs + meta_data.len()].copy_from_slice(meta_data);
193 return Ok(page1);
194 }
195
196 let first_chunk = &meta_data[..META_V3_FIRST_PAYLOAD_CAP];
200 let mut tail = &meta_data[META_V3_FIRST_PAYLOAD_CAP..];
201 let mut chunks: Vec<&[u8]> = Vec::new();
202 while !tail.is_empty() {
203 let take = tail.len().min(META_V3_OVERFLOW_PAYLOAD_CAP);
204 chunks.push(&tail[..take]);
205 tail = &tail[take..];
206 }
207
208 let mut overflow_pages: Vec<Page> = Vec::with_capacity(chunks.len());
209 let mut overflow_ids: Vec<u32> = Vec::with_capacity(chunks.len());
210 for _ in 0..chunks.len() {
211 let pg = pager.allocate_page(PageType::Overflow)?;
212 overflow_ids.push(pg.page_id());
213 overflow_pages.push(pg);
214 }
215
216 for i in 0..chunks.len() {
217 let next = if i + 1 < chunks.len() {
218 overflow_ids[i + 1]
219 } else {
220 0u32
221 };
222 let len = chunks[i].len() as u32;
223 let buf = overflow_pages[i].as_bytes_mut();
224 reddb_file::encode_native_metadata_overflow_continuation_header(
225 &mut buf[cs..cs + META_V3_OVERFLOW_HEADER],
226 reddb_file::NativeMetadataOverflowContinuationHeader {
227 next_overflow_page_id: next,
228 chunk_bytes: len,
229 },
230 )
231 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
232 buf[cs + 8..cs + 8 + chunks[i].len()].copy_from_slice(chunks[i]);
233 }
234 for (idx, page) in overflow_pages.into_iter().enumerate() {
235 let id = overflow_ids[idx];
236 pager.write_page(id, page)?;
237 }
238
239 let format_version = reddb_file::decode_native_paged_metadata_header(meta_data)
241 .ok()
242 .flatten()
243 .map_or(0, |header| header.format_version);
244
245 let buf = page1.as_bytes_mut();
246 reddb_file::encode_native_metadata_overflow_header(
247 &mut buf[cs..cs + META_V3_PAGE1_HEADER],
248 reddb_file::NativeMetadataOverflowHeader {
249 format_version,
250 total_payload_bytes: meta_data.len() as u32,
251 next_overflow_page_id: overflow_ids[0],
252 },
253 )
254 .map_err(|err| PagerError::InvalidDatabase(err.to_string()))?;
255 buf[cs + META_V3_PAGE1_HEADER..cs + META_V3_PAGE1_HEADER + first_chunk.len()]
256 .copy_from_slice(first_chunk);
257
258 Ok(page1)
259}
260
261fn storage_integrity(
262 zone: &str,
263 id: impl Into<String>,
264 collection: Option<String>,
265 detail: impl Into<String>,
266) -> StoreError {
267 StoreError::StorageIntegrity(crate::api::StorageIntegrityError::new(
268 zone, id, collection, detail,
269 ))
270}
271
272fn page_integrity(
273 page_id: u32,
274 collection: Option<String>,
275 detail: impl Into<String>,
276) -> StoreError {
277 storage_integrity("page", page_id.to_string(), collection, detail)
278}
279
280fn read_meta_payload(pager: &Pager) -> Result<Option<Vec<u8>>, StoreError> {
292 let cs = crate::storage::engine::HEADER_SIZE;
293 let meta_page = pager.read_manifest_page().map_err(|err| {
294 storage_integrity(
295 "manifest",
296 "manifest",
297 None,
298 format!("internal manifest zone read failed ({err})"),
299 )
300 })?;
301 let bytes = meta_page.as_bytes();
302 if bytes.len() < cs + 4 {
303 return Ok(Some(bytes.get(cs..).unwrap_or(&[]).to_vec()));
304 }
305 let header = match reddb_file::decode_native_metadata_overflow_header(&bytes[cs..])
306 .map_err(|err| page_integrity(1, None, err.to_string()))?
307 {
308 Some(header) => header,
309 None => {
310 return Ok(Some(bytes[cs..].to_vec()));
311 }
312 };
313 if bytes.len() < cs + META_V3_PAGE1_HEADER {
314 return Err(page_integrity(
315 1,
316 None,
317 "metadata overflow header is truncated",
318 ));
319 }
320 let total = header.total_payload_bytes as usize;
321 let mut next = header.next_overflow_page_id;
322 let mut payload: Vec<u8> = Vec::with_capacity(total);
323 let first_take = total.min(META_V3_FIRST_PAYLOAD_CAP);
324 payload.extend_from_slice(
325 &bytes[cs + META_V3_PAGE1_HEADER..cs + META_V3_PAGE1_HEADER + first_take],
326 );
327 while next != 0 && payload.len() < total {
328 let ov = pager
329 .read_page(next)
330 .map_err(|err| page_integrity(next, None, err.to_string()))?;
331 let ob = ov.as_bytes();
332 if ob.len() < cs + META_V3_OVERFLOW_HEADER {
333 return Err(page_integrity(
334 next,
335 None,
336 "metadata overflow continuation is truncated",
337 ));
338 }
339 let continuation =
340 reddb_file::decode_native_metadata_overflow_continuation_header(&ob[cs..])
341 .map_err(|err| page_integrity(next, None, err.to_string()))?;
342 let nn = continuation.next_overflow_page_id;
343 let len = continuation.chunk_bytes as usize;
344 let remaining = total - payload.len();
345 let take = len.min(remaining).min(META_V3_OVERFLOW_PAYLOAD_CAP);
346 payload.extend_from_slice(
347 &ob[cs + META_V3_OVERFLOW_HEADER..cs + META_V3_OVERFLOW_HEADER + take],
348 );
349 next = nn;
350 }
351 Ok(Some(payload))
352}
353
354impl UnifiedStore {
355 fn checkpoint_tree(
359 &self,
360 pager: &Arc<Pager>,
361 manager: &SegmentManager,
362 previous: Option<&Arc<BTree>>,
363 mut encode: impl FnMut(&UnifiedEntity, Option<Metadata>) -> Option<Vec<u8>>,
364 ) -> Result<Arc<BTree>, StoreError> {
365 let mut cursor = previous
366 .map(|tree| tree.cursor_first())
367 .transpose()
368 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
369 let mut matched = 0;
370 let mut builder = None;
371 let mut failure = None;
372 manager.for_each_entity_with_metadata_in_id_order(|entity, metadata| {
373 let Some(value) = encode(entity, metadata) else {
374 return true;
375 };
376 let record = (entity.id.raw().to_be_bytes().to_vec(), value);
377 let result = (|| {
378 if builder.is_none() {
379 if let Some(cursor) = cursor.as_mut() {
380 if cursor
381 .next()
382 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
383 .as_ref()
384 == Some(&record)
385 {
386 matched += 1;
387 return Ok(());
388 }
389 }
390 let mut replacement = CheckpointTreeBuilder::new(pager);
391 if let Some(previous) = previous {
392 replacement.copy_prefix(previous, matched)?;
393 }
394 builder = Some(replacement);
395 }
396 builder
397 .as_mut()
398 .expect("replacement initialized")
399 .push(record)
400 })();
401 if let Err(error) = result {
402 failure = Some(error);
403 return false;
404 }
405 true
406 });
407 if let Some(error) = failure {
408 return Err(error);
409 }
410 if builder.is_none() {
411 if let (Some(previous), Some(cursor)) = (previous, cursor.as_mut()) {
412 if cursor
413 .next()
414 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
415 .is_none()
416 {
417 return Ok(Arc::clone(previous));
418 }
419 }
420 let mut replacement = CheckpointTreeBuilder::new(pager);
421 if let Some(previous) = previous {
422 replacement.copy_prefix(previous, matched)?;
423 }
424 builder = Some(replacement);
425 }
426 let mut builder = builder.expect("replacement or reused tree");
427 builder.flush()?;
428 Ok(builder.tree)
429 }
430
431 fn publish_reclaimable_trees(
435 &self,
436 pager: &Pager,
437 metadata: &mut Vec<u8>,
438 live: &HashMap<String, Arc<BTree>>,
439 ) -> Result<(), StoreError> {
440 let mut retired = self.retired_btrees.lock();
441 let mut roots = Vec::new();
442 let mut reclaim = Vec::new();
443 let mut remove = std::collections::HashSet::new();
444 for (index, entry) in retired.iter().enumerate() {
445 if live.values().any(|tree| Arc::ptr_eq(tree, &entry.tree)) {
446 remove.insert(index);
447 continue;
448 }
449 if entry.published
450 && Arc::strong_count(&entry.tree) == 1
451 && !entry.tree.has_retirement_readers()
452 {
453 reclaim.extend(
454 entry
455 .tree
456 .retired_pages()
457 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?,
458 );
459 remove.insert(index);
460 } else if entry.tree.root_page_id() != 0 {
461 roots.push(entry.tree.root_page_id());
462 }
463 }
464 roots.sort_unstable();
465 roots.dedup();
466 reddb_file::encode_native_paged_retired_trees(metadata, &roots)
467 .map_err(|e| StoreError::Serialization(e.to_string()))?;
468 publish_paged_metadata(pager, metadata)
471 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
472 let mut index = 0;
473 retired.retain_mut(|entry| {
474 let keep = !remove.contains(&index);
475 entry.published = true;
476 index += 1;
477 keep
478 });
479 if !reclaim.is_empty() {
480 pager
481 .free_retired_pages(&reclaim)
482 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
483 pager
484 .sync()
485 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
486 }
487 Ok(())
488 }
489
490 pub(super) fn retire_btree(&self, tree: Arc<BTree>) {
491 let mut retired = self.retired_btrees.lock();
492 if tree.root_page_id() != 0 && !retired.iter().any(|entry| Arc::ptr_eq(&entry.tree, &tree))
493 {
494 retired.push(RetiredBTree {
495 tree,
496 published: false,
497 });
498 }
499 }
500
501 pub(crate) fn note_paged_insert(
505 &self,
506 collection: &str,
507 manager: &Arc<SegmentManager>,
508 before: &[(u64, u64)],
509 id: EntityId,
510 ) {
511 let after = manager.checkpoint_revisions();
512 if before.len() != after.len() {
513 return;
514 }
515 let mut changed = 0;
516 for ((old_id, old_revision), (new_id, new_revision)) in before.iter().zip(&after) {
517 if old_id != new_id {
518 return;
519 }
520 if old_revision != new_revision {
521 if old_revision.checked_add(1) != Some(*new_revision) {
522 return;
523 }
524 changed += 1;
525 }
526 }
527 if changed != 1 {
528 return;
529 }
530 let mut versions = self.checkpoint_versions.write();
531 let Some(previous) = versions.get_mut(collection) else {
532 return;
533 };
534 if previous.manager.ptr_eq(&Arc::downgrade(manager))
535 && previous.segments == before
536 && previous.format_version == self.format_version()
537 && !previous.pending_vault.contains(&id)
538 && !previous.aborted.contains(&0)
539 {
540 previous.segments = after;
541 }
542 }
543
544 pub(crate) fn mark_paged_registry_dirty(&self) {
545 self.paged_registry_dirty.store(true, Ordering::Release);
546 }
547
548 pub(crate) fn get_or_create_btree(&self, collection: &str) -> Option<Arc<BTree>> {
554 let pager = self.pager.as_ref()?;
555 if let Some(btree) = self.btree_indices.read().get(collection).cloned() {
556 return Some(btree);
557 }
558 let mut write = self.btree_indices.write();
559 let btree = write
560 .entry(collection.to_string())
561 .or_insert_with(|| Arc::new(BTree::new(Arc::clone(pager))))
562 .clone();
563 Some(btree)
564 }
565
566 pub(crate) fn flush_paged_state(&self) -> Result<(), StoreError> {
567 let Some(pager) = &self.pager else {
568 return Ok(());
569 };
570
571 if self.paged_registry_dirty.load(Ordering::Acquire) {
572 self.flush_paged_registry()?;
573 self.paged_registry_dirty.store(false, Ordering::Release);
574 return Ok(());
575 }
576
577 pager
578 .flush()
579 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))
580 }
581
582 pub(crate) fn flush_paged_registry(&self) -> Result<(), StoreError> {
583 let _vault_guard = self.vault_publication_lock.lock();
584 let Some(pager) = &self.pager else {
585 return Ok(());
586 };
587
588 match pager.read_page(1) {
589 Ok(_) => {}
590 Err(PagerError::PageNotFound(_)) => {
591 let meta_page = pager
592 .allocate_page(crate::storage::engine::PageType::Header)
593 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
594 pager
595 .write_page(meta_page.page_id(), meta_page)
596 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
597 }
598 Err(e) => {
599 return Err(StoreError::Io(std::io::Error::other(e.to_string())));
600 }
601 }
602
603 let format_version = STORE_VERSION_V11;
604 self.set_format_version(format_version);
605
606 let collections = self.collections.read();
607 let btree_indices = self.btree_indices.read();
608 let mut collection_roots = Vec::with_capacity(collections.len());
609 for (name, _) in collections.iter() {
610 let root_page = btree_indices
611 .get(name)
612 .map_or(0, |btree| btree.root_page_id());
613 collection_roots.push((name.clone(), root_page));
614 }
615 drop(btree_indices);
616 drop(collections);
617
618 let mut meta_data = Vec::with_capacity(4096);
619 reddb_file::encode_native_paged_metadata_header(
620 &mut meta_data,
621 reddb_file::NativePagedMetadataHeader {
622 format_version,
623 collection_count: collection_roots.len() as u32,
624 },
625 );
626 for (name, root_page) in &collection_roots {
627 reddb_file::encode_native_paged_collection_root(&mut meta_data, name, *root_page);
628 }
629
630 let cross_refs = self.cross_refs.read();
631 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
632 meta_data.extend_from_slice(&(total_refs as u32).to_le_bytes());
633 for (source_id, refs) in cross_refs.iter() {
634 for (target_id, ref_type, collection) in refs {
635 reddb_file::encode_native_paged_cross_ref(
636 &mut meta_data,
637 source_id.raw(),
638 target_id.raw(),
639 ref_type.to_byte(),
640 collection,
641 );
642 }
643 }
644
645 self.publish_reclaimable_trees(pager, &mut meta_data, &self.btree_indices.read())?;
646
647 Ok(())
648 }
649
650 pub fn pager(&self) -> Option<&Arc<Pager>> {
652 self.pager.as_ref()
653 }
654
655 pub fn page_cache_bytes_in_use(&self) -> u64 {
659 self.pager.as_ref().map_or(0, |pager| {
660 pager.cache_len() as u64 * crate::storage::memory_pools::PAGE_CACHE_PAGE_SIZE_BYTES
661 })
662 }
663
664 pub fn wal_buffer_bytes_in_use(&self) -> u64 {
667 self.commit
668 .as_ref()
669 .map_or(0, |commit| commit.buffered_bytes())
670 }
671
672 pub fn segment_memory_bytes(&self) -> u64 {
676 let managers: Vec<Arc<SegmentManager>> =
677 self.collections.read().values().map(Arc::clone).collect();
678
679 managers
680 .iter()
681 .map(|manager| manager.resident_bytes())
682 .fold(0, u64::saturating_add)
683 }
684
685 pub fn config(&self) -> &UnifiedStoreConfig {
689 &self.config
690 }
691
692 pub fn with_config(config: UnifiedStoreConfig) -> Self {
693 Self {
694 config,
695 format_version: AtomicU32::new(STORE_VERSION_V11),
696 next_entity_id: AtomicU64::new(1),
697 collections: RwLock::new(HashMap::new()),
698 cross_refs: RwLock::new(HashMap::new()),
699 reverse_refs: RwLock::new(HashMap::new()),
700 pager: None,
701 db_path: None,
702 btree_indices: RwLock::new(HashMap::new()),
703 checkpoint_versions: RwLock::new(HashMap::new()),
704 retired_btrees: parking_lot::Mutex::new(Vec::new()),
705 context_index: ContextIndex::new(),
706 entity_cache: EntityCache::new(),
707 graph_label_index: RwLock::new(HashMap::new()),
708 paged_registry_dirty: AtomicBool::new(false),
709 commit: None,
710 unindex_cross_refs_fast_path: AtomicU64::new(0),
711 replayed_turbo_inserts: parking_lot::Mutex::new(HashMap::new()),
712 replayed_probabilistic_deltas: parking_lot::Mutex::new(Vec::new()),
713 aux_metadata: RwLock::new(Vec::new()),
714 snapshot_manager: std::sync::OnceLock::new(),
715 embedded_commit_session: std::sync::OnceLock::new(),
716 vault_publication_lock: parking_lot::ReentrantMutex::new(()),
717 pending_vault_versions: RwLock::new(std::collections::HashSet::new()),
718 }
719 }
720
721 pub fn open(path: impl AsRef<Path>) -> Result<Self, StoreError> {
739 Self::open_with_config(path, UnifiedStoreConfig::default())
740 }
741
742 pub fn open_with_config(
743 path: impl AsRef<Path>,
744 config: UnifiedStoreConfig,
745 ) -> Result<Self, StoreError> {
746 let path = path.as_ref();
747 let mut pager_config = PagerConfig::default();
748 if let Some(slots) = config.page_cache_slots {
752 pager_config.cache_size = slots;
753 }
754 if matches!(
762 std::env::var("REDDB_DOUBLE_WRITE").ok().as_deref(),
763 Some("0") | Some("false") | Some("off")
764 ) {
765 pager_config.double_write = false;
766 }
767 let pager = Pager::open(path, pager_config)
768 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
769
770 let wal_path = reddb_file::layout::unified_wal_path(path);
771 let commit = Some(Arc::new(
774 StoreCommitCoordinator::open(wal_path, config.durability_mode, config.group_commit)
775 .map_err(StoreError::Io)?,
776 ));
777
778 let store = Self {
779 config,
780 format_version: AtomicU32::new(STORE_VERSION_V11),
781 next_entity_id: AtomicU64::new(1),
782 collections: RwLock::new(HashMap::new()),
783 cross_refs: RwLock::new(HashMap::new()),
784 reverse_refs: RwLock::new(HashMap::new()),
785 pager: Some(Arc::new(pager)),
786 db_path: Some(path.to_path_buf()),
787 btree_indices: RwLock::new(HashMap::new()),
788 checkpoint_versions: RwLock::new(HashMap::new()),
789 retired_btrees: parking_lot::Mutex::new(Vec::new()),
790 context_index: ContextIndex::new(),
791 entity_cache: EntityCache::new(),
792 graph_label_index: RwLock::new(HashMap::new()),
793 paged_registry_dirty: AtomicBool::new(false),
794 commit,
795 unindex_cross_refs_fast_path: AtomicU64::new(0),
796 replayed_turbo_inserts: parking_lot::Mutex::new(HashMap::new()),
797 replayed_probabilistic_deltas: parking_lot::Mutex::new(Vec::new()),
798 aux_metadata: RwLock::new(Vec::new()),
799 snapshot_manager: std::sync::OnceLock::new(),
800 embedded_commit_session: std::sync::OnceLock::new(),
801 vault_publication_lock: parking_lot::ReentrantMutex::new(()),
802 pending_vault_versions: RwLock::new(std::collections::HashSet::new()),
803 };
804
805 store.load_from_pages()?;
807 if let Some(commit) = &store.commit {
808 commit.replay_into(&store).map_err(StoreError::Io)?;
809 }
810 store.recover_operational_manifest()?;
811
812 Ok(store)
813 }
814
815 pub(crate) fn reconcile_replayed_btrees(&self) -> Result<(), StoreError> {
819 let Some(pager) = &self.pager else {
820 return Ok(());
821 };
822 let collections = self.collections.read();
823 let mut indices = self.btree_indices.write();
824 for (name, manager) in collections.iter() {
825 let tree =
826 self.checkpoint_tree(pager, manager, indices.get(name), |entity, metadata| {
827 Some(Self::serialize_entity_record(
828 entity,
829 metadata.as_ref(),
830 self.format_version(),
831 ))
832 })?;
833 if indices
834 .get(name)
835 .is_some_and(|previous| Arc::ptr_eq(previous, &tree))
836 {
837 continue;
838 }
839 if let Some(previous) = indices.insert(name.clone(), tree) {
840 self.retire_btree(previous);
841 }
842 self.mark_paged_registry_dirty();
843 }
844 drop(indices);
845 drop(collections);
846 self.flush_paged_state()?;
847 if self.snapshot_manager.get().is_none() && self.pending_vault_versions.read().is_empty() {
850 let collections = self.collections.read();
851 let mut versions = self.checkpoint_versions.write();
852 for (name, manager) in collections.iter() {
853 versions.insert(
854 name.clone(),
855 CheckpointVersion {
856 manager: Arc::downgrade(manager),
857 segments: manager.checkpoint_revisions(),
858 aborted_revision: 0,
859 aborted: Arc::default(),
860 pending_vault: Arc::default(),
861 format_version: self.format_version(),
862 },
863 );
864 }
865 }
866 Ok(())
867 }
868
869 pub(crate) fn recover_operational_manifest(&self) -> Result<(), StoreError> {
870 let Some(path) = &self.db_path else {
871 return Ok(());
872 };
873 let mut collections = self.list_collections();
874 collections.sort();
875 let pending_drops =
876 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
877 .recover_or_bootstrap(&collections)
878 .map_err(|err| storage_integrity("manifest", "current", None, err.to_string()))?;
879 for name in pending_drops {
880 if self.get_collection(&name).is_some() {
881 self.drop_collection(&name)?;
882 }
883 }
884 Ok(())
885 }
886
887 pub(crate) fn prepare_operational_collection_create(
888 &self,
889 name: &str,
890 ) -> Result<Option<reddb_file::PreparedCollectionCreate<reddb_file::StdVfs>>, StoreError> {
891 let Some(path) = &self.db_path else {
892 return Ok(None);
893 };
894 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
895 .prepare_collection_create(name)
896 .map(Some)
897 .map_err(StoreError::Io)
898 }
899
900 pub(crate) fn prepare_operational_collection_drop(
901 &self,
902 name: &str,
903 ) -> Result<Option<reddb_file::PreparedCollectionDrop<reddb_file::StdVfs>>, StoreError> {
904 let Some(path) = &self.db_path else {
905 return Ok(None);
906 };
907 crate::storage::operational_manifest::OperationalManifest::for_db_path(path)
908 .prepare_collection_drop(name)
909 .map(Some)
910 .map_err(StoreError::Io)
911 }
912
913 fn load_from_pages(&self) -> Result<(), StoreError> {
917 let pager = match &self.pager {
918 Some(p) => p,
919 None => return Ok(()), };
921
922 let page_count = pager.page_count().map_err(|e| {
924 StoreError::Io(std::io::Error::other(format!(
925 "failed to read page count: {}",
926 e
927 )))
928 })?;
929 if page_count <= 1 {
930 return Ok(());
932 }
933
934 if let Some(content_vec) = read_meta_payload(pager)? {
939 let content: &[u8] = &content_vec;
940 if content.len() >= 4 {
941 let mut pos = 0;
942 let mut format_version = STORE_VERSION_V1;
943
944 let collection_count = if let Some(header) =
945 reddb_file::decode_native_paged_metadata_header(content)
946 .map_err(|err| StoreError::Serialization(err.to_string()))?
947 {
948 format_version = header.format_version;
949 pos += reddb_file::METADATA_HEADER_BYTES;
950 header.collection_count as usize
951 } else {
952 let count = u32::from_le_bytes([
953 content[pos],
954 content[pos + 1],
955 content[pos + 2],
956 content[pos + 3],
957 ]) as usize;
958 pos += 4;
959 count
960 };
961
962 self.set_format_version(format_version);
963
964 if pos > content.len() {
965 return Ok(());
966 }
967
968 for _ in 0..collection_count {
970 let root = reddb_file::decode_native_paged_collection_root(content, &mut pos)
971 .map_err(|err| page_integrity(1, None, err.to_string()))?;
972 let root_page = root.root_page;
974 let name = root.collection;
975
976 let _ = self.create_collection_in_memory(&name);
980
981 if root_page > 0 {
983 let btree = BTree::with_root(Arc::clone(pager), root_page);
984
985 let mut cursor = btree.cursor_first().map_err(|err| {
987 page_integrity(root_page, Some(name.clone()), err.to_string())
988 })?;
989 let manager = self.get_collection(&name);
990 loop {
991 let next = cursor.next().map_err(|err| {
992 page_integrity(root_page, Some(name.clone()), err.to_string())
993 })?;
994 let Some((key, value)) = next else {
995 break;
996 };
997 let (entity, metadata) =
999 Self::deserialize_entity_record(&value, self.format_version())
1000 .map_err(|err| {
1001 page_integrity(
1002 root_page,
1003 Some(name.clone()),
1004 err.to_string(),
1005 )
1006 })?;
1007 if let Some(m) = &manager {
1008 let id = entity.id;
1009 if let EntityKind::TableRow { row_id, .. } = &entity.kind {
1010 m.register_row_id(*row_id);
1011 }
1012 self.context_index.index_entity(&name, &entity);
1013 m.restore_snapshot_entity(entity.clone())?;
1014 if let Some(metadata) = metadata {
1015 let _ = m.set_metadata(id, metadata);
1016 }
1017 self.register_entity_id(id);
1018 if self.config.auto_index_refs {
1019 self.index_cross_refs(&entity, &name)?;
1020 }
1021 }
1022 }
1023
1024 self.btree_indices
1026 .write()
1027 .insert(name.clone(), Arc::new(btree));
1028 if format_version == STORE_VERSION_V11 {
1029 if let Some(manager) = &manager {
1030 self.checkpoint_versions.write().insert(
1031 name,
1032 CheckpointVersion {
1033 manager: Arc::downgrade(manager),
1034 segments: manager.checkpoint_revisions(),
1035 aborted_revision: 0,
1036 aborted: Arc::default(),
1037 pending_vault: Arc::default(),
1038 format_version,
1039 },
1040 );
1041 }
1042 }
1043 }
1044 }
1045
1046 if format_version >= STORE_VERSION_V2 && pos + 4 <= content.len() {
1047 let cross_ref_count = u32::from_le_bytes([
1048 content[pos],
1049 content[pos + 1],
1050 content[pos + 2],
1051 content[pos + 3],
1052 ]) as usize;
1053 pos += 4;
1054
1055 for _ in 0..cross_ref_count {
1056 let Ok(cross_ref) =
1057 reddb_file::decode_native_paged_cross_ref(content, &mut pos)
1058 else {
1059 break;
1060 };
1061 let source_id = EntityId::new(cross_ref.source_id);
1062 let target_id = EntityId::new(cross_ref.target_id);
1063 let ref_type = RefType::from_byte(cross_ref.ref_type);
1064 let target_collection = cross_ref.target_collection;
1065
1066 self.cross_refs.write().entry(source_id).or_default().push((
1067 target_id,
1068 ref_type,
1069 target_collection.clone(),
1070 ));
1071
1072 if let Some((collection, mut entity)) = self.get_any(source_id) {
1073 let exists = entity.cross_refs().iter().any(|xref| {
1074 xref.target == target_id
1075 && xref.ref_type == ref_type
1076 && xref.target_collection == target_collection
1077 });
1078 if !exists {
1079 entity.cross_refs_mut().push(CrossRef::new(
1080 source_id,
1081 target_id,
1082 target_collection.clone(),
1083 ref_type,
1084 ));
1085 if let Some(manager) = self.get_collection(&collection) {
1086 let _ = manager.update(entity);
1087 }
1088 }
1089 }
1090 }
1091 }
1092 let retired = reddb_file::decode_native_paged_retired_trees(content, &mut pos)
1093 .map_err(|e| StoreError::Serialization(e.to_string()))?;
1094 let live_roots: std::collections::HashSet<_> = self
1095 .btree_indices
1096 .read()
1097 .values()
1098 .map(|tree| tree.root_page_id())
1099 .collect();
1100 for root in retired {
1101 if live_roots.contains(&root)
1102 || root
1103 >= pager
1104 .page_count()
1105 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?
1106 {
1107 return Err(page_integrity(
1108 root,
1109 None,
1110 "retired root aliases a live or missing page",
1111 ));
1112 }
1113 self.retired_btrees.lock().push(RetiredBTree {
1114 tree: Arc::new(BTree::with_root(Arc::clone(pager), root)),
1115 published: true,
1116 });
1117 }
1118 }
1119 }
1120
1121 if self.format_version() < STORE_VERSION_V11 {
1122 self.set_format_version(STORE_VERSION_V11);
1123 }
1124
1125 Ok(())
1126 }
1127
1128 pub(crate) fn deserialize_entity(
1130 data: &[u8],
1131 format_version: u32,
1132 ) -> Result<UnifiedEntity, StoreError> {
1133 let mut pos = 0;
1134 Self::read_entity_binary(data, &mut pos, format_version)
1135 .map_err(|e| StoreError::Serialization(e.to_string()))
1136 }
1137
1138 pub(crate) fn serialize_entity(entity: &UnifiedEntity, format_version: u32) -> Vec<u8> {
1140 let mut buf = Vec::with_capacity(256);
1145 Self::write_entity_binary(&mut buf, entity, format_version);
1146 buf
1147 }
1148
1149 pub(crate) fn serialize_entity_record(
1150 entity: &UnifiedEntity,
1151 metadata: Option<&Metadata>,
1152 format_version: u32,
1153 ) -> Vec<u8> {
1154 let entity_bytes = Self::serialize_entity(entity, format_version);
1155 let has_meta = matches!(metadata, Some(m) if !m.fields.is_empty());
1161 if has_meta {
1162 let metadata_bytes = serialize_metadata(metadata);
1163 reddb_file::encode_native_entity_record_frame(&entity_bytes, Some(&metadata_bytes))
1164 } else {
1165 reddb_file::encode_native_entity_record_frame(&entity_bytes, None)
1166 }
1167 }
1168
1169 pub(crate) fn deserialize_entity_record(
1170 data: &[u8],
1171 format_version: u32,
1172 ) -> Result<(UnifiedEntity, Option<Metadata>), StoreError> {
1173 let Some(frame) = reddb_file::decode_native_entity_record_frame(data)
1174 .map_err(|err| StoreError::Serialization(err.to_string()))?
1175 else {
1176 return Self::deserialize_entity(data, format_version).map(|entity| (entity, None));
1177 };
1178
1179 let entity = Self::deserialize_entity(frame.entity, format_version)?;
1180 let metadata = if frame.metadata.is_empty() {
1181 None
1182 } else {
1183 let metadata = deserialize_metadata(frame.metadata)?;
1184 if metadata.is_empty() {
1185 None
1186 } else {
1187 Some(metadata)
1188 }
1189 };
1190
1191 Ok((entity, metadata))
1192 }
1193
1194 pub fn persist(&self) -> Result<(), StoreError> {
1199 let _vault_guard = self.vault_publication_lock.lock();
1200 let pending_vault_versions = self.pending_vault_versions.read();
1201 let pager = match &self.pager {
1202 Some(p) => p,
1203 None => {
1204 if let Some(path) = &self.db_path {
1206 return self
1207 .save_to_file(path)
1208 .map_err(|e| StoreError::Serialization(e.to_string()));
1209 }
1210 return Err(StoreError::Io(std::io::Error::other(
1211 "No pager or path configured for persistence",
1212 )));
1213 }
1214 };
1215
1216 match pager.read_page(1) {
1217 Ok(_) => {}
1218 Err(PagerError::PageNotFound(_)) => {
1219 let meta_page = pager
1220 .allocate_page(crate::storage::engine::PageType::Header)
1221 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
1222 pager
1223 .write_page(meta_page.page_id(), meta_page)
1224 .map_err(|e| StoreError::Io(std::io::Error::other(e.to_string())))?;
1225 }
1226 Err(e) => {
1227 return Err(StoreError::Io(std::io::Error::other(e.to_string())));
1228 }
1229 }
1230
1231 if let Some(commit) = &self.commit {
1232 commit.force_sync().map_err(StoreError::Io)?;
1233 }
1234
1235 let collections = self.collections.read();
1236 let mut btree_indices = self.btree_indices.write();
1237
1238 let mut collection_roots: Vec<(String, u32)> = Vec::new();
1240
1241 let mut rebuilt_indices = HashMap::with_capacity(collections.len());
1244 let previous_versions = self.checkpoint_versions.read();
1245 let mut checkpoint_versions = HashMap::with_capacity(collections.len());
1246 let pending_vault = Arc::new(pending_vault_versions.clone());
1247 let (aborted_revision, aborted) = self.snapshot_manager.get().map_or_else(
1248 || (0, Default::default()),
1249 |manager| manager.checkpoint_aborted_state(),
1250 );
1251 let aborted = Arc::new(aborted);
1252 for (name, manager) in collections.iter() {
1253 let version = CheckpointVersion {
1254 manager: Arc::downgrade(manager),
1255 segments: manager.checkpoint_revisions(),
1256 aborted_revision,
1257 aborted: Arc::clone(&aborted),
1258 pending_vault: Arc::clone(&pending_vault),
1259 format_version: self.format_version(),
1260 };
1261 if let (Some(previous), Some(tree)) =
1262 (previous_versions.get(name), btree_indices.get(name))
1263 {
1264 if previous.manager.ptr_eq(&version.manager)
1265 && previous.segments == version.segments
1266 && previous.format_version == version.format_version
1267 && checkpoint_visibility_matches(previous, &version, manager)
1268 {
1269 collection_roots.push((name.clone(), tree.root_page_id()));
1270 rebuilt_indices.insert(name.clone(), Arc::clone(tree));
1271 checkpoint_versions.insert(name.clone(), version);
1272 continue;
1273 }
1274 }
1275 checkpoint_versions.insert(name.clone(), version);
1276 let btree = self.checkpoint_tree(
1277 pager,
1278 manager,
1279 btree_indices.get(name),
1280 |entity, metadata| {
1281 #[cfg(test)]
1282 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(count.get() + 1));
1283 if pending_vault_versions.contains(&entity.id)
1284 || (matches!(entity.data, EntityData::Row(_) | EntityData::Vector(_))
1285 && aborted.contains(&entity.xmin))
1286 {
1287 return None;
1288 }
1289 let mut normalized = std::borrow::Cow::Borrowed(entity);
1290 if matches!(entity.data, EntityData::Row(_)) && aborted.contains(&entity.xmax) {
1291 normalized.to_mut().xmax = 0;
1292 }
1293 Some(Self::serialize_entity_record(
1294 &normalized,
1295 metadata.as_ref(),
1296 self.format_version(),
1297 ))
1298 },
1299 )?;
1300
1301 collection_roots.push((name.clone(), btree.root_page_id()));
1302 rebuilt_indices.insert(name.clone(), btree);
1303 }
1304
1305 let mut meta_data = Vec::with_capacity(4096);
1307
1308 let format_version = STORE_VERSION_V11;
1309 self.set_format_version(format_version);
1310
1311 reddb_file::encode_native_paged_metadata_header(
1312 &mut meta_data,
1313 reddb_file::NativePagedMetadataHeader {
1314 format_version,
1315 collection_count: collection_roots.len() as u32,
1316 },
1317 );
1318
1319 for (name, root_page) in &collection_roots {
1321 reddb_file::encode_native_paged_collection_root(&mut meta_data, name, *root_page);
1322 }
1323
1324 let cross_refs = self.cross_refs.read();
1326 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
1327 meta_data.extend_from_slice(&(total_refs as u32).to_le_bytes());
1328 for (source_id, refs) in cross_refs.iter() {
1329 for (target_id, ref_type, collection) in refs {
1330 reddb_file::encode_native_paged_cross_ref(
1331 &mut meta_data,
1332 source_id.raw(),
1333 target_id.raw(),
1334 ref_type.to_byte(),
1335 collection,
1336 );
1337 }
1338 }
1339
1340 for (name, previous) in btree_indices.iter() {
1341 if !rebuilt_indices
1342 .get(name)
1343 .is_some_and(|tree| Arc::ptr_eq(tree, previous))
1344 {
1345 self.retire_btree(Arc::clone(previous));
1346 }
1347 }
1348 self.publish_reclaimable_trees(pager, &mut meta_data, &rebuilt_indices)?;
1349
1350 *btree_indices = rebuilt_indices;
1353 drop(previous_versions);
1354 *self.checkpoint_versions.write() = checkpoint_versions;
1355
1356 if let Some(commit) = &self.commit {
1357 commit.truncate().map_err(StoreError::Io)?;
1358 }
1359
1360 Ok(())
1361 }
1362
1363 pub fn is_paged(&self) -> bool {
1365 self.pager.is_some()
1366 }
1367
1368 pub(crate) fn collection_root_page(&self, collection: &str) -> Option<u32> {
1371 self.btree_indices
1372 .read()
1373 .get(collection)
1374 .map(|btree| btree.root_page_id())
1375 .filter(|root| *root != 0)
1376 }
1377
1378 pub fn db_path(&self) -> Option<&Path> {
1380 self.db_path.as_deref()
1381 }
1382}
1383
1384fn serialize_metadata(metadata: Option<&Metadata>) -> Vec<u8> {
1385 let Some(metadata) = metadata else {
1386 return Vec::new();
1387 };
1388 if metadata.is_empty() {
1389 return Vec::new();
1390 }
1391
1392 let mut entries: Vec<_> = metadata.iter().collect();
1393 entries.sort_by_key(|(a, _)| *a);
1394
1395 let mut buf = Vec::new();
1396 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
1397 for (key, value) in entries {
1398 write_string(&mut buf, key);
1399 write_metadata_value(&mut buf, value);
1400 }
1401 buf
1402}
1403
1404fn deserialize_metadata(data: &[u8]) -> Result<Metadata, StoreError> {
1405 let mut pos = 0usize;
1406 let count = read_u32(data, &mut pos)? as usize;
1407 let mut metadata = Metadata::new();
1408 for _ in 0..count {
1409 let key = read_string(data, &mut pos)?;
1410 let value = read_metadata_value(data, &mut pos)?;
1411 metadata.set(key, value);
1412 }
1413 Ok(metadata)
1414}
1415
1416fn write_string(buf: &mut Vec<u8>, value: &str) {
1417 reddb_file::encode_native_len_prefixed_str(buf, value);
1418}
1419
1420fn write_bytes(buf: &mut Vec<u8>, value: &[u8]) {
1421 reddb_file::encode_native_len_prefixed_bytes(buf, value);
1422}
1423
1424fn write_ref_target(buf: &mut Vec<u8>, target: &crate::storage::unified::RefTarget) {
1425 use crate::storage::unified::RefTarget;
1426
1427 match target {
1428 RefTarget::TableRow { table, row_id } => {
1429 buf.push(0);
1430 write_string(buf, table);
1431 buf.extend_from_slice(&row_id.to_le_bytes());
1432 }
1433 RefTarget::Node {
1434 collection,
1435 node_id,
1436 } => {
1437 buf.push(1);
1438 write_string(buf, collection);
1439 buf.extend_from_slice(&node_id.raw().to_le_bytes());
1440 }
1441 RefTarget::Edge {
1442 collection,
1443 edge_id,
1444 } => {
1445 buf.push(2);
1446 write_string(buf, collection);
1447 buf.extend_from_slice(&edge_id.raw().to_le_bytes());
1448 }
1449 RefTarget::Vector {
1450 collection,
1451 vector_id,
1452 } => {
1453 buf.push(3);
1454 write_string(buf, collection);
1455 buf.extend_from_slice(&vector_id.raw().to_le_bytes());
1456 }
1457 RefTarget::Entity {
1458 collection,
1459 entity_id,
1460 } => {
1461 buf.push(4);
1462 write_string(buf, collection);
1463 buf.extend_from_slice(&entity_id.raw().to_le_bytes());
1464 }
1465 }
1466}
1467
1468fn write_metadata_value(buf: &mut Vec<u8>, value: &MetadataValue) {
1469 match value {
1470 MetadataValue::Null => buf.push(0),
1471 MetadataValue::Bool(v) => {
1472 buf.push(1);
1473 buf.push(u8::from(*v));
1474 }
1475 MetadataValue::Int(v) => {
1476 buf.push(2);
1477 buf.extend_from_slice(&v.to_le_bytes());
1478 }
1479 MetadataValue::Float(v) => {
1480 buf.push(3);
1481 buf.extend_from_slice(&v.to_le_bytes());
1482 }
1483 MetadataValue::String(v) => {
1484 buf.push(4);
1485 write_string(buf, v);
1486 }
1487 MetadataValue::Bytes(v) => {
1488 buf.push(5);
1489 write_bytes(buf, v);
1490 }
1491 MetadataValue::Array(values) => {
1492 buf.push(6);
1493 buf.extend_from_slice(&(values.len() as u32).to_le_bytes());
1494 for value in values {
1495 write_metadata_value(buf, value);
1496 }
1497 }
1498 MetadataValue::Object(values) => {
1499 buf.push(7);
1500 let mut entries: Vec<_> = values.iter().collect();
1501 entries.sort_by_key(|(a, _)| *a);
1502 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
1503 for (key, value) in entries {
1504 write_string(buf, key);
1505 write_metadata_value(buf, value);
1506 }
1507 }
1508 MetadataValue::Timestamp(v) => {
1509 buf.push(8);
1510 buf.extend_from_slice(&v.to_le_bytes());
1511 }
1512 MetadataValue::Geo { lat, lon } => {
1513 buf.push(9);
1514 buf.extend_from_slice(&lat.to_le_bytes());
1515 buf.extend_from_slice(&lon.to_le_bytes());
1516 }
1517 MetadataValue::Reference(target) => {
1518 buf.push(10);
1519 write_ref_target(buf, target);
1520 }
1521 MetadataValue::References(targets) => {
1522 buf.push(11);
1523 buf.extend_from_slice(&(targets.len() as u32).to_le_bytes());
1524 for target in targets {
1525 write_ref_target(buf, target);
1526 }
1527 }
1528 }
1529}
1530
1531fn read_exact_slice<'a>(
1532 data: &'a [u8],
1533 pos: &mut usize,
1534 len: usize,
1535) -> Result<&'a [u8], StoreError> {
1536 if *pos + len > data.len() {
1537 return Err(StoreError::Serialization(
1538 "truncated metadata payload".to_string(),
1539 ));
1540 }
1541 let slice = &data[*pos..*pos + len];
1542 *pos += len;
1543 Ok(slice)
1544}
1545
1546fn read_u32(data: &[u8], pos: &mut usize) -> Result<u32, StoreError> {
1547 let bytes = read_exact_slice(data, pos, 4)?;
1548 let mut raw = [0u8; 4];
1549 raw.copy_from_slice(bytes);
1550 Ok(u32::from_le_bytes(raw))
1551}
1552
1553fn read_u64(data: &[u8], pos: &mut usize) -> Result<u64, StoreError> {
1554 let bytes = read_exact_slice(data, pos, 8)?;
1555 let mut raw = [0u8; 8];
1556 raw.copy_from_slice(bytes);
1557 Ok(u64::from_le_bytes(raw))
1558}
1559
1560fn read_i64(data: &[u8], pos: &mut usize) -> Result<i64, StoreError> {
1561 let bytes = read_exact_slice(data, pos, 8)?;
1562 let mut raw = [0u8; 8];
1563 raw.copy_from_slice(bytes);
1564 Ok(i64::from_le_bytes(raw))
1565}
1566
1567fn read_f64(data: &[u8], pos: &mut usize) -> Result<f64, StoreError> {
1568 let bytes = read_exact_slice(data, pos, 8)?;
1569 let mut raw = [0u8; 8];
1570 raw.copy_from_slice(bytes);
1571 Ok(f64::from_le_bytes(raw))
1572}
1573
1574fn read_u8(data: &[u8], pos: &mut usize) -> Result<u8, StoreError> {
1575 let bytes = read_exact_slice(data, pos, 1)?;
1576 Ok(bytes[0])
1577}
1578
1579fn read_string(data: &[u8], pos: &mut usize) -> Result<String, StoreError> {
1580 reddb_file::decode_native_len_prefixed_string(data, pos)
1581 .map_err(|err| StoreError::Serialization(err.to_string()))
1582}
1583
1584fn read_bytes(data: &[u8], pos: &mut usize) -> Result<Vec<u8>, StoreError> {
1585 reddb_file::decode_native_len_prefixed_bytes(data, pos)
1586 .map(|bytes| bytes.to_vec())
1587 .map_err(|err| StoreError::Serialization(err.to_string()))
1588}
1589
1590fn read_ref_target(
1591 data: &[u8],
1592 pos: &mut usize,
1593) -> Result<crate::storage::unified::RefTarget, StoreError> {
1594 use crate::storage::unified::RefTarget;
1595
1596 match read_u8(data, pos)? {
1597 0 => Ok(RefTarget::TableRow {
1598 table: read_string(data, pos)?,
1599 row_id: read_u64(data, pos)?,
1600 }),
1601 1 => Ok(RefTarget::Node {
1602 collection: read_string(data, pos)?,
1603 node_id: EntityId::new(read_u64(data, pos)?),
1604 }),
1605 2 => Ok(RefTarget::Edge {
1606 collection: read_string(data, pos)?,
1607 edge_id: EntityId::new(read_u64(data, pos)?),
1608 }),
1609 3 => Ok(RefTarget::Vector {
1610 collection: read_string(data, pos)?,
1611 vector_id: EntityId::new(read_u64(data, pos)?),
1612 }),
1613 4 => Ok(RefTarget::Entity {
1614 collection: read_string(data, pos)?,
1615 entity_id: EntityId::new(read_u64(data, pos)?),
1616 }),
1617 tag => Err(StoreError::Serialization(format!(
1618 "unknown metadata ref target tag {tag}"
1619 ))),
1620 }
1621}
1622
1623fn read_metadata_value(data: &[u8], pos: &mut usize) -> Result<MetadataValue, StoreError> {
1624 match read_u8(data, pos)? {
1625 0 => Ok(MetadataValue::Null),
1626 1 => Ok(MetadataValue::Bool(read_u8(data, pos)? != 0)),
1627 2 => Ok(MetadataValue::Int(read_i64(data, pos)?)),
1628 3 => Ok(MetadataValue::Float(read_f64(data, pos)?)),
1629 4 => Ok(MetadataValue::String(read_string(data, pos)?)),
1630 5 => Ok(MetadataValue::Bytes(read_bytes(data, pos)?)),
1631 6 => {
1632 let count = read_u32(data, pos)? as usize;
1633 let mut values = Vec::with_capacity(count);
1634 for _ in 0..count {
1635 values.push(read_metadata_value(data, pos)?);
1636 }
1637 Ok(MetadataValue::Array(values))
1638 }
1639 7 => {
1640 let count = read_u32(data, pos)? as usize;
1641 let mut values = std::collections::HashMap::with_capacity(count);
1642 for _ in 0..count {
1643 let key = read_string(data, pos)?;
1644 let value = read_metadata_value(data, pos)?;
1645 values.insert(key, value);
1646 }
1647 Ok(MetadataValue::Object(values))
1648 }
1649 8 => Ok(MetadataValue::Timestamp(read_u64(data, pos)?)),
1650 9 => Ok(MetadataValue::Geo {
1651 lat: read_f64(data, pos)?,
1652 lon: read_f64(data, pos)?,
1653 }),
1654 10 => Ok(MetadataValue::Reference(read_ref_target(data, pos)?)),
1655 11 => {
1656 let count = read_u32(data, pos)? as usize;
1657 let mut targets = Vec::with_capacity(count);
1658 for _ in 0..count {
1659 targets.push(read_ref_target(data, pos)?);
1660 }
1661 Ok(MetadataValue::References(targets))
1662 }
1663 tag => Err(StoreError::Serialization(format!(
1664 "unknown metadata value tag {tag}"
1665 ))),
1666 }
1667}
1668
1669#[cfg(test)]
1670mod checkpoint_work_tests {
1671 use super::*;
1672
1673 #[test]
1674 fn retired_tree_pages_remain_pinned_by_a_cursor() {
1675 let directory = tempfile::tempdir().expect("temporary store");
1676 let store = UnifiedStore::open(&directory.path().join("data.rdb")).expect("paged store");
1677 for id in 1..=100 {
1678 store.insert_auto("rows", row(id, 1)).expect("seed");
1679 }
1680 store.persist().expect("initial checkpoint");
1681 let tree = store
1682 .btree_indices
1683 .read()
1684 .get("rows")
1685 .cloned()
1686 .expect("tree");
1687 let mut cursor = tree.cursor_first().expect("cursor");
1688 drop(tree);
1689 let manager = store.get_collection("rows").expect("rows");
1690 for value in 2..=6 {
1691 manager.update(row(1, value)).expect("change");
1692 store.persist().expect("checkpoint with old reader");
1693 }
1694 let mut count = 0;
1695 while let Some((_, bytes)) = cursor.next().expect("pinned tree must stay readable") {
1696 let (entity, _) =
1697 UnifiedStore::deserialize_entity_record(&bytes, store.format_version())
1698 .expect("decode");
1699 assert_eq!(
1700 entity.data.as_row().expect("row").columns,
1701 [Value::Integer(1)]
1702 );
1703 count += 1;
1704 }
1705 assert_eq!(count, 100);
1706 drop(cursor);
1707 store.persist().expect("reclaim after reader release");
1708 assert!(store.retired_btrees.lock().is_empty());
1709 }
1710
1711 #[test]
1712 fn checkpoint_payload_buffer_is_bounded_for_a_large_collection() {
1713 let directory = tempfile::tempdir().expect("temporary store");
1714 let store = UnifiedStore::open(&directory.path().join("data.rdb")).expect("paged store");
1715 store.create_collection("rows").expect("collection");
1716 let manager = store.get_collection("rows").expect("rows");
1717 for id in (1..=1200).rev() {
1718 manager
1719 .insert(UnifiedEntity::table_row(
1720 EntityId::new(id),
1721 "rows",
1722 id,
1723 vec![Value::text("x".repeat(4096))],
1724 ))
1725 .expect("seed");
1726 }
1727 CHECKPOINT_BUFFERED_BYTES.with(|peak| peak.set(0));
1728 store.persist().expect("bounded checkpoint");
1729 assert!(CHECKPOINT_BUFFERED_BYTES.with(|peak| peak.get()) <= CHECKPOINT_BATCH_BYTES);
1730 assert!(CHECKPOINT_BUFFERED_BYTES.with(|peak| peak.get()) > 0);
1731 assert_eq!(store.get_collection("rows").expect("rows").count(), 1200);
1732 }
1733
1734 #[test]
1735 fn unchanged_checkpoint_does_not_serialize_collection_rows() {
1736 let directory = tempfile::tempdir().expect("temporary store");
1737 let store = UnifiedStore::open(&directory.path().join("data.rdb")).expect("paged store");
1738 store.create_collection("rows").expect("collection");
1739 for id in 1..=40 {
1740 store
1741 .insert(
1742 "rows",
1743 UnifiedEntity::table_row(
1744 EntityId::new(id),
1745 "rows",
1746 id,
1747 vec![Value::Integer(id as i64)],
1748 ),
1749 )
1750 .expect("seed row");
1751 }
1752 store.persist().expect("initial checkpoint");
1753 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1754 store.persist().expect("unchanged checkpoint");
1755 assert_eq!(
1756 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()),
1757 0,
1758 "unchanged collections must not hold the writer fence while serializing every row"
1759 );
1760 drop(store);
1761 let reopened = UnifiedStore::open(&directory.path().join("data.rdb")).expect("reopen");
1762 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1763 reopened.persist().expect("unchanged recovered generation");
1764 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1765 }
1766
1767 #[test]
1768 fn paged_reopen_preserves_stored_sequence_identity() {
1769 let directory = tempfile::tempdir().expect("temporary store");
1770 let path = directory.path().join("data.rdb");
1771 let store = UnifiedStore::open(&path).expect("paged store");
1772 store.create_collection("rows").expect("collection");
1773 for id in [30, 10, 20] {
1774 store.insert("rows", row(id, id as i64)).expect("insert");
1775 }
1776 store.persist().expect("checkpoint");
1777 let sequences = [10, 20, 30].map(|id| {
1778 store
1779 .get("rows", EntityId::new(id))
1780 .expect("row")
1781 .sequence_id
1782 });
1783 drop(store);
1784 let reopened = UnifiedStore::open(&path).expect("reopen");
1785 let recovered = [10, 20, 30].map(|id| {
1786 reopened
1787 .get("rows", EntityId::new(id))
1788 .expect("row")
1789 .sequence_id
1790 });
1791 assert_eq!(
1792 recovered, sequences,
1793 "opening a checkpoint must not rewrite stored row identities"
1794 );
1795 }
1796
1797 #[test]
1798 fn auto_insert_persists_the_assigned_sequence_identity() {
1799 let directory = tempfile::tempdir().expect("temporary store");
1800 let store = UnifiedStore::open(&directory.path().join("data.rdb")).expect("paged store");
1801 for id in 1u64..=3 {
1802 store
1803 .insert_auto("rows", row(id, id as i64))
1804 .expect("insert");
1805 }
1806 let manager = store.get_collection("rows").expect("rows");
1807 let tree = store
1808 .btree_indices
1809 .read()
1810 .get("rows")
1811 .cloned()
1812 .expect("tree");
1813 for id in 1u64..=3 {
1814 let bytes = tree
1815 .get(&id.to_be_bytes())
1816 .expect("lookup")
1817 .expect("stored row");
1818 let (persisted, _) =
1819 UnifiedStore::deserialize_entity_record(&bytes, store.format_version())
1820 .expect("decode");
1821 assert_eq!(
1822 persisted.sequence_id,
1823 manager
1824 .get(EntityId::new(id))
1825 .expect("live row")
1826 .sequence_id
1827 );
1828 }
1829 }
1830
1831 #[test]
1832 fn completed_inline_insert_reuses_history_but_not_untracked_edits() {
1833 let directory = tempfile::tempdir().expect("temporary store");
1834 let path = directory.path().join("data.rdb");
1835 let store = UnifiedStore::open(&path).expect("paged store");
1836 store.insert_auto("rows", row(1, 1)).expect("seed");
1837 store.persist().expect("checkpoint");
1838 store.insert_auto("rows", row(2, 2)).expect("append");
1839 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1840 store.persist().expect("inline append checkpoint");
1841 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1842 store
1843 .get_collection("rows")
1844 .expect("rows")
1845 .update(row(1, 99))
1846 .expect("untracked edit");
1847 store.insert_auto("rows", row(3, 3)).expect("next append");
1848 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1849 store.persist().expect("untracked edit checkpoint");
1850 assert!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()) > 0);
1851 drop(store);
1852 let reopened = UnifiedStore::open(&path).expect("reopen");
1853 assert_eq!(
1854 reopened
1855 .get("rows", EntityId::new(1))
1856 .expect("row")
1857 .data
1858 .as_row()
1859 .expect("table")
1860 .columns,
1861 vec![Value::Integer(99)]
1862 );
1863 }
1864
1865 #[test]
1866 fn replayed_trees_are_reusable_after_durable_reconciliation() {
1867 let directory = tempfile::tempdir().expect("temporary store");
1868 let path = directory.path().join("data.rdb");
1869 let store = UnifiedStore::open(&path).expect("paged store");
1870 store.insert_auto("rows", row(1, 1)).expect("seed");
1871 store.persist().expect("checkpoint");
1872 store
1873 .insert_auto("rows", row(2, 2))
1874 .expect("WAL-backed append");
1875 drop(store);
1876 let reopened = UnifiedStore::open(&path).expect("replay");
1877 assert!(reopened.get("rows", EntityId::new(2)).is_some());
1878 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1879 reopened.persist().expect("checkpoint reconciled tree");
1880 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1881 }
1882
1883 fn row(id: u64, value: i64) -> UnifiedEntity {
1884 UnifiedEntity::table_row(EntityId::new(id), "rows", id, vec![Value::Integer(value)])
1885 }
1886
1887 #[test]
1888 fn checkpoint_reuse_tracks_manager_updates_metadata_deletes_and_bulk_inserts() {
1889 let directory = tempfile::tempdir().expect("temporary store");
1890 let path = directory.path().join("data.rdb");
1891 let store = UnifiedStore::open(&path).expect("paged store");
1892 store.create_collection("rows").expect("collection");
1893 store.insert("rows", row(1, 1)).expect("seed");
1894 store.persist().expect("initial checkpoint");
1895 let manager = store.get_collection("rows").expect("rows");
1896 manager.update(row(1, 2)).expect("direct update");
1897 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1898 store.persist().expect("updated row checkpoint");
1899 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 1);
1900 let mut metadata = Metadata::new();
1901 metadata.set("verified", MetadataValue::Bool(true));
1902 manager
1903 .set_metadata(EntityId::new(1), metadata)
1904 .expect("metadata");
1905 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1906 store.persist().expect("metadata-only checkpoint");
1907 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 1);
1908 manager
1909 .bulk_insert(vec![row(2, 2), row(3, 3)])
1910 .expect("bulk insert");
1911 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1912 store.persist().expect("bulk insert checkpoint");
1913 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 3);
1914 manager.delete(EntityId::new(2)).expect("delete");
1915 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1916 store.persist().expect("changed checkpoint");
1917 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 2);
1918 drop(store);
1919 let recovered = UnifiedStore::open(&path).expect("recover changed rows");
1920 let manager = recovered.get_collection("rows").expect("rows");
1921 assert_eq!(manager.count(), 2);
1922 assert_eq!(
1923 manager
1924 .get(EntityId::new(1))
1925 .expect("row")
1926 .data
1927 .as_row()
1928 .expect("table")
1929 .columns,
1930 vec![Value::Integer(2)]
1931 );
1932 assert!(manager.get(EntityId::new(2)).is_none());
1933 assert!(manager
1934 .get_metadata(EntityId::new(1))
1935 .expect("metadata")
1936 .get("verified")
1937 .is_some());
1938 }
1939
1940 #[test]
1941 fn unrelated_visibility_changes_do_not_serialize_collection_rows() {
1942 let directory = tempfile::tempdir().expect("temporary store");
1943 let store = UnifiedStore::open(&directory.path().join("data.rdb")).expect("paged store");
1944 let snapshots = Arc::new(crate::storage::transaction::snapshot::SnapshotManager::new());
1945 assert!(store.snapshot_manager.set(Arc::clone(&snapshots)).is_ok());
1946 store.create_collection("rows").expect("collection");
1947 for id in 1..=40 {
1948 store.insert("rows", row(id, id as i64)).expect("seed");
1949 }
1950 store.persist().expect("initial checkpoint");
1951 let unrelated = snapshots.begin();
1952 snapshots.rollback(unrelated);
1953 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1954 store.persist().expect("unrelated rollback checkpoint");
1955 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1956
1957 store
1958 .pending_vault_versions
1959 .write()
1960 .insert(EntityId::new(100));
1961 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1962 store.persist().expect("unrelated vault checkpoint");
1963 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1964 store.pending_vault_versions.write().clear();
1965 CHECKPOINT_SERIALIZED_ROWS.with(|count| count.set(0));
1966 store
1967 .persist()
1968 .expect("unrelated vault publication checkpoint");
1969 assert_eq!(CHECKPOINT_SERIALIZED_ROWS.with(|count| count.get()), 0);
1970 }
1971
1972 #[test]
1973 fn rollback_invalidates_reuse_without_a_segment_mutation() {
1974 let directory = tempfile::tempdir().expect("temporary store");
1975 let path = directory.path().join("data.rdb");
1976 let store = UnifiedStore::open(&path).expect("paged store");
1977 let snapshots = Arc::new(crate::storage::transaction::snapshot::SnapshotManager::new());
1978 assert!(store.snapshot_manager.set(Arc::clone(&snapshots)).is_ok());
1979 store.create_collection("rows").expect("collection");
1980 let xid = snapshots.begin();
1981 let mut pending = row(1, 1);
1982 pending.xmin = xid;
1983 store.insert("rows", pending).expect("version");
1984 store.persist().expect("before rollback");
1985 snapshots.rollback(xid);
1986 store.persist().expect("after rollback");
1987 drop(store);
1988 let recovered = UnifiedStore::open(&path).expect("recover");
1989 assert_eq!(recovered.get_collection("rows").expect("rows").count(), 0);
1990 }
1991
1992 #[test]
1993 fn rolled_back_delete_is_normalized_without_a_segment_mutation() {
1994 let directory = tempfile::tempdir().expect("temporary store");
1995 let path = directory.path().join("data.rdb");
1996 let store = UnifiedStore::open(&path).expect("paged store");
1997 let snapshots = Arc::new(crate::storage::transaction::snapshot::SnapshotManager::new());
1998 assert!(store.snapshot_manager.set(Arc::clone(&snapshots)).is_ok());
1999 store.create_collection("rows").expect("collection");
2000 let xid = snapshots.begin();
2001 let mut deleted = row(1, 1);
2002 deleted.xmax = xid;
2003 store.insert("rows", deleted).expect("version");
2004 store.persist().expect("before rollback");
2005 snapshots.rollback(xid);
2006 store.persist().expect("after rollback");
2007 drop(store);
2008 let recovered = UnifiedStore::open(&path).expect("recover");
2009 let manager = recovered.get_collection("rows").expect("rows");
2010 assert_eq!(manager.get(EntityId::new(1)).expect("retained row").xmax, 0);
2011 }
2012
2013 #[test]
2014 fn publishing_a_pending_vault_version_invalidates_reuse() {
2015 let directory = tempfile::tempdir().expect("temporary store");
2016 let path = directory.path().join("data.rdb");
2017 let store = UnifiedStore::open(&path).expect("paged store");
2018 store.create_collection("rows").expect("collection");
2019 store.insert("rows", row(1, 1)).expect("row");
2020 store
2021 .pending_vault_versions
2022 .write()
2023 .insert(EntityId::new(1));
2024 store.persist().expect("exclude pending vault version");
2025 store
2026 .pending_vault_versions
2027 .write()
2028 .remove(&EntityId::new(1));
2029 store.persist().expect("include published vault version");
2030 drop(store);
2031 let recovered = UnifiedStore::open(&path).expect("recover");
2032 assert_eq!(recovered.get_collection("rows").expect("rows").count(), 1);
2033 }
2034}