1#![allow(clippy::arc_with_non_send_sync)]
45
46use crate::{
47 io_yield_one, turso_assert, turso_assert_eq, turso_assert_greater_than,
48 types::{IOCompletions, IOResult},
49 util::IOExt as _,
50};
51use branches::{mark_unlikely, unlikely};
52use bytemuck::{Pod, Zeroable};
53use pack1::{I32BE, U16BE, U32BE};
54use tracing::{instrument, Level};
55
56use super::pager::PageRef;
57pub use super::pager::{PageContent, PageInner};
58use super::wal::{OverflowFallbackCoverage, TursoRwLock, WalSharedMetadata, WalSharedRuntime};
59use crate::error::LimboError;
60use crate::fast_lock::SpinLock;
61use crate::io::{Buffer, Completion, FileSyncType, ReadComplete};
62use crate::numeric::Numeric;
63use crate::storage::btree::{payload_overflow_threshold_max, payload_overflow_threshold_min};
64use crate::storage::buffer_pool::BufferPool;
65use crate::storage::database::{DatabaseStorage, EncryptionOrChecksum};
66use crate::storage::pager::Pager;
67use crate::storage::wal::READMARK_NOT_USED;
68use crate::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
69use crate::sync::Arc;
70use crate::sync::RwLock;
71use crate::types::{SerialType, SerialTypeKind, TextRef, TextSubtype, ValueRef};
72use crate::{bail_corrupt_error, CompletionError, File, IOContext, Result, WalFileShared};
73use rustc_hash::FxHashMap;
74use std::collections::BTreeMap;
75use std::pin::Pin;
76
77pub const MINIMUM_CELL_SIZE: usize = 4;
79
80pub const CELL_PTR_SIZE_BYTES: usize = 2;
81pub const INTERIOR_PAGE_HEADER_SIZE_BYTES: usize = 12;
82pub const LEAF_PAGE_HEADER_SIZE_BYTES: usize = 8;
83pub const LEFT_CHILD_PTR_SIZE_BYTES: usize = 4;
84
85pub const FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR: usize = 0;
90pub const FREELIST_TRUNK_OFFSET_LEAF_COUNT: usize = 4;
91pub const FREELIST_TRUNK_OFFSET_FIRST_LEAF_PTR: usize = 8;
92pub const FREELIST_TRUNK_HEADER_SIZE: usize = 8;
93pub const FREELIST_LEAF_PTR_SIZE: usize = 4;
94
95#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy, Debug)]
96#[repr(transparent)]
97pub struct PageSize(U16BE);
99
100impl PageSize {
101 pub const MIN: u32 = 512;
102 pub const MAX: u32 = 65536;
103 pub const DEFAULT: u16 = 4096;
104
105 pub const fn new(size: u32) -> Option<Self> {
107 if size < PageSize::MIN || size > PageSize::MAX {
108 return None;
109 }
110
111 if size.count_ones() != 1 {
113 return None;
114 }
115
116 if size == PageSize::MAX {
117 return Some(Self(U16BE::new(1)));
119 }
120
121 Some(Self(U16BE::new(size as u16)))
122 }
123
124 pub fn new_from_header_u16(value: u16) -> Result<Self> {
127 match value {
128 1 => Ok(Self(U16BE::new(1))),
129 n => {
130 let Some(size) = Self::new(n as u32) else {
131 bail_corrupt_error!("invalid page size in database header: {n}");
132 };
133
134 Ok(size)
135 }
136 }
137 }
138
139 pub const fn get(self) -> u32 {
140 match self.0.get() {
141 1 => Self::MAX,
142 v => v as u32,
143 }
144 }
145
146 pub const fn get_raw(self) -> u16 {
148 self.0.get()
149 }
150}
151
152impl Default for PageSize {
153 fn default() -> Self {
154 Self(U16BE::new(Self::DEFAULT))
155 }
156}
157
158#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy, Debug)]
159#[repr(transparent)]
160pub struct CacheSize(I32BE);
162
163impl CacheSize {
164 pub const DEFAULT: i32 = -2000;
167
168 pub const MIN: i64 = super::page_cache::MINIMUM_PAGE_CACHE_SIZE_IN_PAGES as i64;
170
171 pub const MAX_SAFE: i64 = 2147450880;
173
174 pub const fn new(size: i32) -> Self {
175 match size {
176 Self::DEFAULT => Self(I32BE::new(0)),
177 v => Self(I32BE::new(v)),
178 }
179 }
180
181 pub const fn get(self) -> i32 {
182 match self.0.get() {
183 0 => Self::DEFAULT,
184 v => v,
185 }
186 }
187}
188
189impl Default for CacheSize {
190 fn default() -> Self {
191 Self(I32BE::new(Self::DEFAULT))
192 }
193}
194
195#[derive(PartialEq, Eq, Clone, Copy, Debug)]
197#[repr(u8)]
198pub enum Version {
199 Legacy = 1,
200 Wal = 2,
201 Mvcc = 255,
202}
203
204impl Version {
205 #[inline]
206 pub fn wal(&self) -> bool {
207 matches!(self, Self::Wal)
208 }
209
210 #[inline]
211 pub fn mvcc(&self) -> bool {
212 matches!(self, Self::Mvcc)
213 }
214
215 #[inline]
216 pub fn legacy(&self) -> bool {
217 matches!(self, Self::Legacy)
218 }
219}
220
221impl TryFrom<u8> for Version {
222 type Error = u8;
223
224 fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
225 match value {
226 1 => Ok(Version::Legacy),
227 2 => Ok(Version::Wal),
228 255 => Ok(Version::Mvcc),
229 v => Err(v),
230 }
231 }
232}
233
234#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy)]
237#[repr(transparent)]
238pub struct RawVersion(pub u8);
239
240impl RawVersion {
241 pub fn to_version(self) -> std::result::Result<Version, u8> {
242 Version::try_from(self.0)
243 }
244}
245
246impl From<Version> for RawVersion {
247 fn from(v: Version) -> Self {
248 Self(v as u8)
249 }
250}
251
252impl std::fmt::Debug for RawVersion {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 match self.to_version() {
255 Ok(v) => write!(f, "{v:?}"),
256 Err(v) => write!(f, "RawVersion::Invalid({v})"),
257 }
258 }
259}
260
261#[derive(PartialEq, Eq, Zeroable, Pod, Clone, Copy)]
262#[repr(transparent)]
263pub struct TextEncoding(U32BE);
265
266impl TextEncoding {
267 #![allow(non_upper_case_globals)]
268 pub const Unset: Self = Self(U32BE::new(0));
271 pub const Utf8: Self = Self(U32BE::new(1));
272 pub const Utf16Le: Self = Self(U32BE::new(2));
273 pub const Utf16Be: Self = Self(U32BE::new(3));
274
275 pub fn is_utf8(&self) -> bool {
276 self == &Self::Utf8 || self == &Self::Unset
277 }
278}
279
280impl std::fmt::Display for TextEncoding {
281 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282 match *self {
283 Self::Utf8 => f.write_str("UTF-8"),
284 Self::Utf16Le => f.write_str("UTF-16le"),
285 Self::Utf16Be => f.write_str("UTF-16be"),
286 Self(v) => write!(f, "TextEncoding::Invalid({})", v.get()),
287 }
288 }
289}
290
291impl std::fmt::Debug for TextEncoding {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 match *self {
294 Self::Utf8 => f.write_str("TextEncoding::Utf8"),
295 Self::Utf16Le => f.write_str("TextEncoding::Utf16Le"),
296 Self::Utf16Be => f.write_str("TextEncoding::Utf16Be"),
297 Self(v) => write!(f, "TextEncoding::Invalid({})", v.get()),
298 }
299 }
300}
301
302impl Default for TextEncoding {
303 fn default() -> Self {
304 Self::Utf8
305 }
306}
307
308#[derive(Pod, Zeroable, Clone, Copy, Debug)]
309#[cfg_attr(clt_turso_tests, derive(PartialEq, Eq))]
310#[repr(C, packed)]
311pub struct DatabaseHeader {
313 pub magic: [u8; 16],
315 pub page_size: PageSize,
317 pub write_version: RawVersion,
319 pub read_version: RawVersion,
321 pub reserved_space: u8,
323 pub max_embed_frac: u8,
325 pub min_embed_frac: u8,
327 pub leaf_frac: u8,
329 pub change_counter: U32BE,
331 pub database_size: U32BE,
333 pub freelist_trunk_page: U32BE,
335 pub freelist_pages: U32BE,
337 pub schema_cookie: U32BE,
339 pub schema_format: U32BE,
341 pub default_page_cache_size: CacheSize,
343 pub vacuum_mode_largest_root_page: U32BE,
345 pub text_encoding: TextEncoding,
347 pub user_version: I32BE,
349 pub incremental_vacuum_enabled: U32BE,
351 pub application_id: I32BE,
353 _padding: [u8; 20],
355 pub version_valid_for: U32BE,
357 pub version_number: U32BE,
359}
360
361impl DatabaseHeader {
362 pub const PAGE_ID: usize = 1;
363 pub const SIZE: usize = size_of::<Self>();
364
365 const _CHECK: () = {
366 assert!(Self::SIZE == 100);
367 };
368
369 pub fn usable_space(self) -> usize {
370 (self.page_size.get() as usize) - (self.reserved_space as usize)
371 }
372}
373
374impl Default for DatabaseHeader {
375 fn default() -> Self {
376 Self {
377 magic: *b"SQLite format 3\0",
378 page_size: Default::default(),
379 write_version: RawVersion::from(Version::Wal),
380 read_version: RawVersion::from(Version::Wal),
381 reserved_space: 0,
382 max_embed_frac: 64,
383 min_embed_frac: 32,
384 leaf_frac: 32,
385 change_counter: U32BE::new(1),
386 database_size: U32BE::new(0),
387 freelist_trunk_page: U32BE::new(0),
388 freelist_pages: U32BE::new(0),
389 schema_cookie: U32BE::new(0),
390 schema_format: U32BE::new(4), default_page_cache_size: Default::default(),
392 vacuum_mode_largest_root_page: U32BE::new(0),
393 text_encoding: TextEncoding::Utf8,
394 user_version: I32BE::new(0),
395 incremental_vacuum_enabled: U32BE::new(0),
396 application_id: I32BE::new(0),
397 _padding: [0; 20],
398 version_valid_for: U32BE::new(3047000),
399 version_number: U32BE::new(3047000),
400 }
401 }
402}
403
404pub const WAL_HEADER_SIZE: usize = 32;
405pub const WAL_FRAME_HEADER_SIZE: usize = 24;
406pub const WAL_MAGIC_LE: u32 = 0x377f0682;
409pub const WAL_MAGIC_BE: u32 = 0x377f0683;
410
411#[derive(Debug, Clone, Copy)]
415#[repr(C)] pub struct WalHeader {
418 pub magic: u32,
421
422 pub file_format: u32,
424
425 pub page_size: u32,
427
428 pub checkpoint_seq: u32,
430
431 pub salt_1: u32,
434
435 pub salt_2: u32,
438
439 pub checksum_1: u32,
441
442 pub checksum_2: u32,
444}
445
446impl WalHeader {
447 pub const fn new() -> Self {
448 let magic = if cfg!(target_endian = "big") {
449 WAL_MAGIC_BE
450 } else {
451 WAL_MAGIC_LE
452 };
453 WalHeader {
454 magic,
455 file_format: 3007000,
456 page_size: 0, checkpoint_seq: 0, salt_1: 0,
459 salt_2: 0,
460 checksum_1: 0,
461 checksum_2: 0,
462 }
463 }
464}
465
466impl Default for WalHeader {
467 fn default() -> Self {
468 Self::new()
469 }
470}
471
472#[allow(dead_code)]
476#[derive(Debug, Default, Copy, Clone)]
477pub struct WalFrameHeader {
478 pub(crate) page_number: u32,
480
481 pub(crate) db_size: u32,
484
485 pub(crate) salt_1: u32,
487
488 pub(crate) salt_2: u32,
490
491 pub(crate) checksum_1: u32,
493
494 pub(crate) checksum_2: u32,
496}
497
498impl WalFrameHeader {
499 pub fn is_commit_frame(&self) -> bool {
500 self.db_size > 0
501 }
502}
503
504#[repr(u8)]
505#[derive(Debug, PartialEq, Clone, Copy)]
506pub enum PageType {
507 IndexInterior = 2,
508 TableInterior = 5,
509 IndexLeaf = 10,
510 TableLeaf = 13,
511}
512
513impl PageType {
514 pub fn is_table(&self) -> bool {
515 match self {
516 PageType::IndexInterior | PageType::IndexLeaf => false,
517 PageType::TableInterior | PageType::TableLeaf => true,
518 }
519 }
520}
521
522impl TryFrom<u8> for PageType {
523 type Error = LimboError;
524
525 fn try_from(value: u8) -> Result<Self> {
526 match value {
527 2 => Ok(Self::IndexInterior),
528 5 => Ok(Self::TableInterior),
529 10 => Ok(Self::IndexLeaf),
530 13 => Ok(Self::TableLeaf),
531 _ => {
532 mark_unlikely();
533 Err(LimboError::Corrupt(format!("Invalid page type: {value}")))
534 }
535 }
536 }
537}
538
539#[derive(Debug, Clone)]
540pub struct OverflowCell {
541 pub index: usize,
542 pub payload: Pin<Vec<u8>>,
543}
544
545#[instrument(skip_all, level = Level::DEBUG)]
548pub fn begin_read_page(
549 db_file: &dyn DatabaseStorage,
550 buffer_pool: Arc<BufferPool>,
551 page: PageRef,
552 page_idx: usize,
553 allow_empty_read: bool,
554 io_ctx: &IOContext,
555) -> Result<Completion> {
556 tracing::trace!("begin_read_btree_page(page_idx = {})", page_idx);
557 let buf = buffer_pool.get_page();
558 #[allow(clippy::arc_with_non_send_sync)]
559 let buf = Arc::new(buf);
560 let complete = Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {
561 let Ok((buf, bytes_read)) = res else {
562 page.clear_locked();
563 return None; };
565 let buf_len = buf.len();
566 if bytes_read == 0 {
569 if !allow_empty_read {
570 tracing::error!("short read on page {page_idx}: expected {buf_len} bytes, got 0");
571 page.clear_locked();
572 return Some(CompletionError::ShortRead {
573 page_idx,
574 expected: buf_len,
575 actual: 0,
576 });
577 }
578 } else if bytes_read != buf_len as i32 {
579 tracing::error!(
580 "short read on page {page_idx}: expected {buf_len} bytes, got {bytes_read}"
581 );
582 page.clear_locked();
583 return Some(CompletionError::ShortRead {
584 page_idx,
585 expected: buf_len,
586 actual: bytes_read as usize,
587 });
588 }
589 let page = page.clone();
590 let buffer = if bytes_read == 0 {
591 Arc::new(Buffer::new_temporary(0))
592 } else {
593 buf
594 };
595 finish_read_page(page_idx, buffer, page);
596 None
597 });
598 let c = Completion::new_read(buf, complete);
599 db_file.read_page(page_idx, io_ctx, c)
600}
601
602#[instrument(skip_all, level = Level::DEBUG)]
603pub fn finish_read_page(page_idx: usize, buffer: Arc<Buffer>, page: PageRef) {
604 tracing::trace!("finish_read_page(page_idx = {page_idx})");
605 {
606 let inner = page.get();
607 inner.buffer = Some(buffer);
608 page.clear_locked();
609 page.set_loaded();
610 page.clear_wal_tag();
613 }
614}
615
616#[instrument(skip_all, level = Level::DEBUG)]
617pub fn begin_write_btree_page(pager: &Pager, page: &PageRef) -> Result<Completion> {
618 tracing::trace!("begin_write_btree_page(page={})", page.get().id);
619 let page_source = &pager.db_file;
620 let page_finish = page.clone();
621
622 let page_id = page.get().id;
623 tracing::trace!("begin_write_btree_page(page_id={})", page_id);
624
625 let buffer = page.get().buffer.clone().expect("buffer not loaded");
626 let buf_len = buffer.len();
627
628 let write_complete = {
629 Box::new(move |res: Result<i32, CompletionError>| {
630 let Ok(bytes_written) = res else {
631 return;
632 };
633 tracing::trace!("finish_write_btree_page");
634
635 page_finish.clear_dirty();
636 turso_assert!(
637 bytes_written == buf_len as i32,
638 "wrote({bytes_written}) != expected({buf_len})"
639 );
640 })
641 };
642 let c = Completion::new_write(write_complete);
643 let io_ctx = pager.io_ctx.read();
644 page_source.write_page(page_id, buffer, &io_ctx, c)
645}
646
647#[instrument(skip_all, level = Level::DEBUG)]
648pub fn write_pages_vectored(
659 pager: &Pager,
660 batch: BTreeMap<usize, Arc<Buffer>>,
661 done_flag: Arc<AtomicBool>,
662 err: Arc<crate::sync::OnceLock<CompletionError>>,
663) -> Result<Vec<Completion>> {
664 if batch.is_empty() {
665 done_flag.store(true, Ordering::Release);
666 return Ok(Vec::new());
667 }
668
669 let page_sz = pager.get_page_size_unchecked().get() as usize;
670
671 let mut run_count = 0;
672 let mut prev_id = None;
673 for &id in batch.keys() {
674 if let Some(prev) = prev_id {
675 if id != prev + 1 {
676 run_count += 1;
677 }
678 } else {
679 run_count = 1;
680 }
681 prev_id = Some(id);
682 }
683
684 let runs_left = Arc::new(AtomicUsize::new(run_count));
685
686 const EST_BUFF_CAPACITY: usize = 32;
687 let mut run_bufs = Vec::with_capacity(EST_BUFF_CAPACITY);
688 let mut run_start_id: Option<usize> = None;
689 let mut completions = Vec::with_capacity(run_count);
690
691 let mut iter = batch.iter().peekable();
692 while let Some((id, buffer)) = iter.next() {
693 if run_start_id.is_none() {
694 run_start_id = Some(*id);
695 }
696 run_bufs.push(buffer.clone());
697
698 let is_end_of_run = iter.peek().is_none_or(|(next_id, _)| **next_id != id + 1);
699 if !is_end_of_run {
700 continue;
701 }
702
703 let start_id = run_start_id.take().expect("start id");
704 let runs_left_cl = runs_left.clone();
705 let done_cl = done_flag.clone();
706 let err_cl = err.clone();
707
708 let expected_bytes = (page_sz * run_bufs.len()) as i32;
709
710 let cmp = Completion::new_write(move |res| {
711 match res {
713 Ok(n) => {
714 if n != expected_bytes {
715 let _ = err_cl.set(CompletionError::ShortWrite);
716 tracing::error!(
717 "write_pages_vectored: short write: wrote({n}) != expected({expected_bytes})"
718 );
719 }
720 }
721 Err(e) => {
722 tracing::error!("write_pages_vectored: write error: {:?}", e);
723 let _ = err_cl.set(e);
724 }
725 }
726 if runs_left_cl.fetch_sub(1, Ordering::AcqRel) == 1 {
728 tracing::debug!("write_pages_vectored: run complete");
729 done_cl.store(true, Ordering::Release);
730 }
731 });
732 let io_ctx = pager.io_ctx.read();
733 let bufs = std::mem::replace(&mut run_bufs, Vec::with_capacity(EST_BUFF_CAPACITY));
734 match pager
735 .db_file
736 .write_pages(start_id, page_sz, bufs, &io_ctx, cmp)
737 {
738 Ok(c) => completions.push(c),
739 Err(e) => {
740 let _ = err.set(CompletionError::Aborted);
742 done_flag.store(true, Ordering::Release);
743 pager.io.cancel(&completions)?;
744 pager.io.drain_completions(&completions)?;
745 return Err(e);
746 }
747 }
748 }
749 Ok(completions)
750}
751
752#[instrument(skip_all, level = Level::DEBUG)]
753pub fn begin_sync(
754 db_file: &dyn DatabaseStorage,
755 syncing: Arc<AtomicBool>,
756 sync_type: FileSyncType,
757) -> Result<Completion> {
758 turso_assert!(!syncing.load(Ordering::SeqCst));
759 syncing.store(true, Ordering::SeqCst);
760 let completion = Completion::new_sync({
761 let syncing = syncing.clone();
762 move |_| {
763 syncing.store(false, Ordering::SeqCst);
764 }
765 });
766 #[allow(clippy::arc_with_non_send_sync)]
767 db_file.sync(completion, sync_type).inspect_err(|_| {
768 syncing.store(false, Ordering::SeqCst);
769 })
770}
771
772#[allow(clippy::enum_variant_names)]
773#[derive(Debug, Clone)]
774pub enum BTreeCell {
775 TableInteriorCell(TableInteriorCell),
776 TableLeafCell(TableLeafCell),
777 IndexInteriorCell(IndexInteriorCell),
778 IndexLeafCell(IndexLeafCell),
779}
780
781#[derive(Debug, Clone)]
782pub struct TableInteriorCell {
783 pub left_child_page: u32,
784 pub rowid: i64,
785}
786
787#[derive(Debug, Clone)]
788pub struct TableLeafCell {
789 pub rowid: i64,
790 pub payload: &'static [u8],
792 pub payload_size: u64,
794 pub first_overflow_page: Option<u32>,
795}
796
797#[derive(Debug, Clone)]
798pub struct IndexInteriorCell {
799 pub left_child_page: u32,
800 pub payload: &'static [u8],
801 pub payload_size: u64,
803 pub first_overflow_page: Option<u32>,
804}
805
806#[derive(Debug, Clone)]
807pub struct IndexLeafCell {
808 pub payload: &'static [u8],
809 pub payload_size: u64,
811 pub first_overflow_page: Option<u32>,
812}
813
814pub fn read_btree_cell(
817 page: &'static [u8],
818 page_content: &PageContent,
819 pos: usize,
820 usable_size: usize,
821) -> Result<BTreeCell> {
822 let page_type = page_content.page_type()?;
823 let max_local = payload_overflow_threshold_max(page_type, usable_size);
824 let min_local = payload_overflow_threshold_min(page_type, usable_size);
825 match page_type {
826 PageType::IndexInterior => {
827 let mut pos = pos;
828 crate::assert_or_bail_corrupt!(
829 pos + 4 <= page.len(),
830 "cell offset {} out of bounds for page size {}",
831 pos,
832 page.len()
833 );
834 let left_child_page =
835 u32::from_be_bytes([page[pos], page[pos + 1], page[pos + 2], page[pos + 3]]);
836 pos += 4;
837 let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;
838 pos += nr;
839
840 let (overflows, to_read) =
841 payload_overflows(payload_size as usize, max_local, min_local, usable_size);
842 let to_read = if overflows { to_read } else { page.len() - pos };
843
844 crate::assert_or_bail_corrupt!(
845 pos + to_read <= page.len(),
846 "payload range {}..{} out of bounds for page size {}",
847 pos,
848 pos + to_read,
849 page.len()
850 );
851 let (payload, first_overflow_page) =
852 read_payload(&page[pos..pos + to_read], payload_size as usize)?;
853 Ok(BTreeCell::IndexInteriorCell(IndexInteriorCell {
854 left_child_page,
855 payload,
856 first_overflow_page,
857 payload_size,
858 }))
859 }
860 PageType::TableInterior => {
861 let mut pos = pos;
862 crate::assert_or_bail_corrupt!(
863 pos + 4 <= page.len(),
864 "cell offset {} out of bounds for page size {}",
865 pos,
866 page.len()
867 );
868 let left_child_page =
869 u32::from_be_bytes([page[pos], page[pos + 1], page[pos + 2], page[pos + 3]]);
870 pos += 4;
871 let (rowid, _) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;
872 Ok(BTreeCell::TableInteriorCell(TableInteriorCell {
873 left_child_page,
874 rowid: rowid as i64,
875 }))
876 }
877 PageType::IndexLeaf => {
878 let mut pos = pos;
879 let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;
880 pos += nr;
881
882 let (overflows, to_read) =
883 payload_overflows(payload_size as usize, max_local, min_local, usable_size);
884 let to_read = if overflows { to_read } else { page.len() - pos };
885
886 crate::assert_or_bail_corrupt!(
887 pos + to_read <= page.len(),
888 "payload range {}..{} out of bounds for page size {}",
889 pos,
890 pos + to_read,
891 page.len()
892 );
893 let (payload, first_overflow_page) =
894 read_payload(&page[pos..pos + to_read], payload_size as usize)?;
895 Ok(BTreeCell::IndexLeafCell(IndexLeafCell {
896 payload,
897 first_overflow_page,
898 payload_size,
899 }))
900 }
901 PageType::TableLeaf => {
902 let mut pos = pos;
903 let (payload_size, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;
904 pos += nr;
905 let (rowid, nr) = read_varint(crate::slice_in_bounds_or_corrupt!(page, pos..))?;
906 pos += nr;
907
908 let (overflows, to_read) =
909 payload_overflows(payload_size as usize, max_local, min_local, usable_size);
910 let to_read = if overflows { to_read } else { page.len() - pos };
911
912 crate::assert_or_bail_corrupt!(
913 pos + to_read <= page.len(),
914 "payload range {}..{} out of bounds for page size {}",
915 pos,
916 pos + to_read,
917 page.len()
918 );
919 let (payload, first_overflow_page) =
920 read_payload(&page[pos..pos + to_read], payload_size as usize)?;
921 Ok(BTreeCell::TableLeafCell(TableLeafCell {
922 rowid: rowid as i64,
923 payload,
924 first_overflow_page,
925 payload_size,
926 }))
927 }
928 }
929}
930
931#[allow(clippy::readonly_write_lock)]
934fn read_payload(
935 unread: &'static [u8],
936 payload_size: usize,
937) -> Result<(&'static [u8], Option<u32>)> {
938 let cell_len = unread.len();
939 if payload_size <= cell_len {
941 Ok((&unread[..payload_size], None))
943 } else {
944 if cell_len < 4 {
946 bail_corrupt_error!(
947 "overflow cell too small: {} bytes, need at least 4",
948 cell_len
949 );
950 }
951 let first_overflow_page = u32::from_be_bytes([
952 unread[cell_len - 4],
953 unread[cell_len - 3],
954 unread[cell_len - 2],
955 unread[cell_len - 1],
956 ]);
957 Ok((&unread[..cell_len - 4], Some(first_overflow_page)))
958 }
959}
960
961#[inline(always)]
962#[allow(dead_code)]
963pub fn validate_serial_type(value: u64) -> Result<()> {
964 if !SerialType::u64_is_valid_serial_type(value) {
965 crate::bail_corrupt_error!("Invalid serial type: {}", value);
966 }
967 Ok(())
968}
969
970#[inline(always)]
973pub fn read_value<'a>(buf: &'a [u8], serial_type: SerialType) -> Result<(ValueRef<'a>, usize)> {
974 match serial_type.kind() {
975 SerialTypeKind::Null => Ok((ValueRef::Null, 0)),
976 SerialTypeKind::I8 => {
977 let val = *buf.first().ok_or_else(|| {
978 mark_unlikely();
979 LimboError::Corrupt("Invalid UInt8 value".into())
980 })?;
981 Ok((ValueRef::Numeric(Numeric::Integer(val as i8 as i64)), 1))
982 }
983 SerialTypeKind::I16 => {
984 let bytes: &[u8; 2] =
985 buf.get(..2)
986 .and_then(|s| s.try_into().ok())
987 .ok_or_else(|| {
988 mark_unlikely();
989 LimboError::Corrupt("Invalid BEInt16 value".into())
990 })?;
991 Ok((
992 ValueRef::Numeric(Numeric::Integer(i16::from_be_bytes(*bytes) as i64)),
993 2,
994 ))
995 }
996 SerialTypeKind::I24 => {
997 let bytes: &[u8; 3] =
998 buf.get(..3)
999 .and_then(|s| s.try_into().ok())
1000 .ok_or_else(|| {
1001 mark_unlikely();
1002 LimboError::Corrupt("Invalid BEInt24 value".into())
1003 })?;
1004 let sign_extension = (bytes[0] as i8 >> 7) as u8;
1005 Ok((
1006 ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([
1007 sign_extension,
1008 bytes[0],
1009 bytes[1],
1010 bytes[2],
1011 ]) as i64)),
1012 3,
1013 ))
1014 }
1015 SerialTypeKind::I32 => {
1016 let bytes: &[u8; 4] =
1017 buf.get(..4)
1018 .and_then(|s| s.try_into().ok())
1019 .ok_or_else(|| {
1020 mark_unlikely();
1021 LimboError::Corrupt("Invalid BEInt32 value".into())
1022 })?;
1023 Ok((
1024 ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes(*bytes) as i64)),
1025 4,
1026 ))
1027 }
1028 SerialTypeKind::I48 => {
1029 let bytes: &[u8; 6] =
1030 buf.get(..6)
1031 .and_then(|s| s.try_into().ok())
1032 .ok_or_else(|| {
1033 mark_unlikely();
1034 LimboError::Corrupt("Invalid BEInt48 value".into())
1035 })?;
1036 let sign_extension = (bytes[0] as i8 >> 7) as u8;
1037 Ok((
1038 ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([
1039 sign_extension,
1040 sign_extension,
1041 bytes[0],
1042 bytes[1],
1043 bytes[2],
1044 bytes[3],
1045 bytes[4],
1046 bytes[5],
1047 ]))),
1048 6,
1049 ))
1050 }
1051 SerialTypeKind::I64 => {
1052 let bytes: &[u8; 8] =
1053 buf.get(..8)
1054 .and_then(|s| s.try_into().ok())
1055 .ok_or_else(|| {
1056 mark_unlikely();
1057 LimboError::Corrupt("Invalid BEInt64 value".into())
1058 })?;
1059 Ok((
1060 ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes(*bytes))),
1061 8,
1062 ))
1063 }
1064 SerialTypeKind::F64 => {
1065 let bytes: &[u8; 8] = buf
1066 .get(..8)
1067 .and_then(|s| s.try_into().ok())
1068 .ok_or_else(|| LimboError::Corrupt("Invalid BEFloat64 value".into()))?;
1069 Ok((ValueRef::from_f64(f64::from_be_bytes(*bytes)), 8))
1070 }
1071 SerialTypeKind::ConstInt0 => Ok((ValueRef::Numeric(Numeric::Integer(0)), 0)),
1072 SerialTypeKind::ConstInt1 => Ok((ValueRef::Numeric(Numeric::Integer(1)), 0)),
1073 SerialTypeKind::Blob => {
1074 let content_size = serial_type.size();
1075 let data = buf.get(..content_size).ok_or_else(|| {
1076 mark_unlikely();
1077 LimboError::Corrupt("Invalid Blob value".into())
1078 })?;
1079 Ok((ValueRef::Blob(data), content_size))
1080 }
1081 SerialTypeKind::Text => {
1082 let content_size = serial_type.size();
1083 let data = buf.get(..content_size).ok_or_else(|| {
1084 mark_unlikely();
1085 LimboError::Corrupt(format!(
1086 "Invalid String value, length {} < expected length {}",
1087 buf.len(),
1088 content_size
1089 ))
1090 })?;
1091 let val = unsafe { std::str::from_utf8_unchecked(data) };
1093 Ok((
1094 ValueRef::Text(TextRef::new(val, TextSubtype::Text)),
1095 content_size,
1096 ))
1097 }
1098 }
1099}
1100
1101pub fn read_value_serial_type<'a>(
1102 buf: &'a [u8],
1103 serial_type: u64,
1104) -> Result<(ValueRef<'a>, usize)> {
1105 match serial_type {
1106 0 => Ok((ValueRef::Null, 0)),
1107 1 => {
1108 if buf.is_empty() {
1109 mark_unlikely();
1110 crate::bail_corrupt_error!("Invalid 1-byte int");
1111 }
1112 Ok((ValueRef::Numeric(Numeric::Integer(buf[0] as i8 as i64)), 1))
1113 }
1114 2 => {
1115 if buf.len() < 2 {
1116 mark_unlikely();
1117 crate::bail_corrupt_error!("Invalid 2-byte int");
1118 }
1119 Ok((
1120 ValueRef::Numeric(Numeric::Integer(i16::from_be_bytes([buf[0], buf[1]]) as i64)),
1121 2,
1122 ))
1123 }
1124 3 => {
1125 if buf.len() < 3 {
1126 mark_unlikely();
1127 crate::bail_corrupt_error!("Invalid 3-byte int");
1128 }
1129 let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };
1130 Ok((
1131 ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([
1132 sign_extension,
1133 buf[0],
1134 buf[1],
1135 buf[2],
1136 ]) as i64)),
1137 3,
1138 ))
1139 }
1140 4 => {
1141 if buf.len() < 4 {
1142 mark_unlikely();
1143 crate::bail_corrupt_error!("Invalid 4-byte int");
1144 }
1145 Ok((
1146 ValueRef::Numeric(Numeric::Integer(i32::from_be_bytes([
1147 buf[0], buf[1], buf[2], buf[3],
1148 ]) as i64)),
1149 4,
1150 ))
1151 }
1152 5 => {
1153 if buf.len() < 6 {
1154 mark_unlikely();
1155 crate::bail_corrupt_error!("Invalid 6-byte int");
1156 }
1157 let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };
1158 Ok((
1159 ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([
1160 sign_extension,
1161 sign_extension,
1162 buf[0],
1163 buf[1],
1164 buf[2],
1165 buf[3],
1166 buf[4],
1167 buf[5],
1168 ]))),
1169 6,
1170 ))
1171 }
1172 6 => {
1173 if buf.len() < 8 {
1174 mark_unlikely();
1175 crate::bail_corrupt_error!("Invalid 8-byte int");
1176 }
1177 Ok((
1178 ValueRef::Numeric(Numeric::Integer(i64::from_be_bytes([
1179 buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
1180 ]))),
1181 8,
1182 ))
1183 }
1184 7 => {
1185 if buf.len() < 8 {
1186 mark_unlikely();
1187 crate::bail_corrupt_error!("Invalid 8-byte float");
1188 }
1189 Ok((
1190 ValueRef::from_f64(f64::from_be_bytes([
1191 buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
1192 ])),
1193 8,
1194 ))
1195 }
1196 8 => Ok((ValueRef::Numeric(Numeric::Integer(0)), 0)),
1197 9 => Ok((ValueRef::Numeric(Numeric::Integer(1)), 0)),
1198 n if n >= 12 => match n % 2 {
1199 0 => {
1200 let content_size = ((n - 12) / 2) as usize;
1202 let data = buf.get(..content_size).ok_or_else(|| {
1203 mark_unlikely();
1204 LimboError::Corrupt("Invalid Blob value".into())
1205 })?;
1206 Ok((ValueRef::Blob(data), content_size))
1207 }
1208 1 => {
1209 let content_size = ((n - 13) / 2) as usize;
1211 let data = buf.get(..content_size).ok_or_else(|| {
1212 mark_unlikely();
1213 LimboError::Corrupt(format!(
1214 "Invalid String value, length {} < expected length {}",
1215 buf.len(),
1216 content_size
1217 ))
1218 })?;
1219 let val = unsafe { std::str::from_utf8_unchecked(data) };
1221 Ok((
1222 ValueRef::Text(TextRef::new(val, TextSubtype::Text)),
1223 content_size,
1224 ))
1225 }
1226 _ => unreachable!(),
1227 },
1228 _ => {
1229 mark_unlikely();
1230 crate::bail_corrupt_error!("Invalid serial type for integer")
1231 }
1232 }
1233}
1234
1235#[inline(always)]
1236pub fn read_integer(buf: &[u8], serial_type: u8) -> Result<i64> {
1237 match serial_type {
1238 1 => {
1239 if buf.is_empty() {
1240 mark_unlikely();
1241 crate::bail_corrupt_error!("Invalid 1-byte int");
1242 }
1243 Ok(buf[0] as i8 as i64)
1244 }
1245 2 => {
1246 if buf.len() < 2 {
1247 mark_unlikely();
1248 crate::bail_corrupt_error!("Invalid 2-byte int");
1249 }
1250 Ok(i16::from_be_bytes([buf[0], buf[1]]) as i64)
1251 }
1252 3 => {
1253 if buf.len() < 3 {
1254 mark_unlikely();
1255 crate::bail_corrupt_error!("Invalid 3-byte int");
1256 }
1257 let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };
1258 Ok(i32::from_be_bytes([sign_extension, buf[0], buf[1], buf[2]]) as i64)
1259 }
1260 4 => {
1261 if buf.len() < 4 {
1262 mark_unlikely();
1263 crate::bail_corrupt_error!("Invalid 4-byte int");
1264 }
1265 Ok(i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as i64)
1266 }
1267 5 => {
1268 if buf.len() < 6 {
1269 mark_unlikely();
1270 crate::bail_corrupt_error!("Invalid 6-byte int");
1271 }
1272 let sign_extension = if buf[0] <= 0x7F { 0 } else { 0xFF };
1273 Ok(i64::from_be_bytes([
1274 sign_extension,
1275 sign_extension,
1276 buf[0],
1277 buf[1],
1278 buf[2],
1279 buf[3],
1280 buf[4],
1281 buf[5],
1282 ]))
1283 }
1284 6 => {
1285 if buf.len() < 8 {
1286 crate::bail_corrupt_error!("Invalid 8-byte int");
1287 }
1288 Ok(i64::from_be_bytes([
1289 buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
1290 ]))
1291 }
1292 8 => Ok(0),
1293 9 => Ok(1),
1294 _ => {
1295 mark_unlikely();
1296 crate::bail_corrupt_error!("Invalid serial type for integer")
1297 }
1298 }
1299}
1300
1301#[inline(always)]
1304pub fn read_varint(buf: &[u8]) -> Result<(u64, usize)> {
1305 let mut v: u64 = 0;
1306 for i in 0..8 {
1307 match buf.get(i) {
1308 Some(c) => {
1309 v = (v << 7) + (c & 0x7f) as u64;
1310 if (c & 0x80) == 0 {
1311 return Ok((v, i + 1));
1312 }
1313 }
1314 None => {
1315 mark_unlikely();
1316 crate::bail_corrupt_error!("Invalid varint");
1317 }
1318 }
1319 }
1320 match buf.get(8) {
1321 Some(&c) => {
1322 if unlikely((v >> 48) == 0) {
1327 bail_corrupt_error!("Invalid varint");
1328 }
1329 v = (v << 8) + c as u64;
1330 Ok((v, 9))
1331 }
1332 None => {
1333 mark_unlikely();
1334 bail_corrupt_error!("Invalid varint");
1335 }
1336 }
1337}
1338
1339#[inline(always)]
1340pub fn read_varint_partial(buf: &[u8]) -> Result<Option<(u64, usize)>> {
1342 let mut v: u64 = 0;
1343 for i in 0..8 {
1344 let Some(&c) = buf.get(i) else {
1345 return Ok(None);
1346 };
1347 v = (v << 7) + (c & 0x7f) as u64;
1348 if (c & 0x80) == 0 {
1349 return Ok(Some((v, i + 1)));
1350 }
1351 }
1352 let Some(&c) = buf.get(8) else {
1353 return Ok(None);
1354 };
1355 if unlikely((v >> 48) == 0) {
1356 bail_corrupt_error!("Invalid varint");
1357 }
1358 v = (v << 8) + c as u64;
1359 Ok(Some((v, 9)))
1360}
1361
1362#[inline(always)]
1368pub fn varint_len(value: u64) -> usize {
1369 if value <= 0x7f {
1370 1
1371 } else if value > (1u64 << 56) - 1 {
1372 9
1373 } else {
1374 let bits = 64 - value.leading_zeros() as usize;
1375 bits.div_ceil(7)
1376 }
1377}
1378
1379pub fn write_varint(buf: &mut [u8], value: u64) -> usize {
1380 if value <= 0x7f {
1381 buf[0] = (value & 0x7f) as u8;
1382 return 1;
1383 }
1384
1385 if value <= 0x3fff {
1386 buf[0] = (((value >> 7) & 0x7f) | 0x80) as u8;
1387 buf[1] = (value & 0x7f) as u8;
1388 return 2;
1389 }
1390
1391 let mut value = value;
1392 if (value & ((0xff000000_u64) << 32)) > 0 {
1393 buf[8] = value as u8;
1394 value >>= 8;
1395 for i in (0..8).rev() {
1396 buf[i] = ((value & 0x7f) | 0x80) as u8;
1397 value >>= 7;
1398 }
1399 return 9;
1400 }
1401
1402 let mut encoded: [u8; 9] = [0; 9];
1403 let mut bytes = value;
1404 let mut n = 0;
1405 while bytes != 0 {
1406 let v = 0x80 | (bytes & 0x7f);
1407 encoded[n] = v as u8;
1408 bytes >>= 7;
1409 n += 1;
1410 }
1411 encoded[0] &= 0x7f;
1412 for i in 0..n {
1413 buf[i] = encoded[n - 1 - i];
1414 }
1415 n
1416}
1417
1418pub fn write_varint_to_vec(value: u64, payload: &mut Vec<u8>) {
1419 let mut varint = [0u8; 9];
1420 let n = write_varint(&mut varint, value);
1421 payload.extend_from_slice(&varint[0..n]);
1422}
1423
1424pub struct BuildSharedWal {
1435 reader: Option<Arc<StreamingWalReader>>,
1436 wal_file_shared: Arc<RwLock<WalFileShared>>,
1437 file_size: u64,
1438 phase: BuildSharedWalPhase,
1439}
1440
1441#[derive(Clone)]
1442enum BuildSharedWalPhase {
1443 NeedHeaderRead,
1445 AwaitHeader(Completion),
1447 ChunkLoop,
1449 AwaitChunk { completion: Completion, offset: u64 },
1451 Done,
1453}
1454
1455impl BuildSharedWal {
1456 pub fn begin(file: &Arc<dyn File>) -> Result<Self> {
1460 let size = file.size()?;
1461
1462 let header = Arc::new(SpinLock::new(WalHeader::default()));
1463 let read_locks = std::array::from_fn(|_| TursoRwLock::new());
1464 for (i, l) in read_locks.iter().enumerate() {
1465 l.write();
1466 l.set_value_exclusive(if i < 2 { 0 } else { READMARK_NOT_USED });
1467 l.unlock();
1468 }
1469
1470 let wal_file_shared = Arc::new(RwLock::new(WalFileShared {
1471 metadata: WalSharedMetadata {
1472 enabled: AtomicBool::new(true),
1473 wal_header: header.clone(),
1474 min_frame: AtomicU64::new(0),
1475 max_frame: AtomicU64::new(0),
1476 nbackfills: AtomicU64::new(0),
1477 transaction_count: AtomicU64::new(0),
1478 last_checksum: (0, 0),
1479 loaded: AtomicBool::new(false),
1480 loaded_from_disk_scan: AtomicBool::new(true),
1481 initialized: AtomicBool::new(false),
1482 },
1483 runtime: WalSharedRuntime {
1484 authority_reconciliation: Default::default(),
1485 frame_cache: Arc::new(SpinLock::new(FxHashMap::default())),
1486 frame_cache_high_water: AtomicU64::new(0),
1487 file: Some(file.clone()),
1488 read_locks,
1489 vacuum_lock: TursoRwLock::new(),
1490 write_lock: TursoRwLock::new(),
1491 checkpoint_lock: TursoRwLock::new(),
1492 epoch: AtomicU32::new(0),
1493 overflow_fallback_coverage: Arc::new(SpinLock::new(
1494 OverflowFallbackCoverage::default(),
1495 )),
1496 },
1497 }));
1498
1499 if size < WAL_HEADER_SIZE as u64 {
1500 wal_file_shared
1501 .write()
1502 .metadata
1503 .loaded
1504 .store(true, Ordering::SeqCst);
1505 return Ok(Self {
1506 reader: None,
1507 wal_file_shared,
1508 file_size: size,
1509 phase: BuildSharedWalPhase::Done,
1510 });
1511 }
1512
1513 let reader = Arc::new(StreamingWalReader::new(
1514 file.clone(),
1515 wal_file_shared.clone(),
1516 header,
1517 size,
1518 ));
1519
1520 Ok(Self {
1521 reader: Some(reader),
1522 wal_file_shared,
1523 file_size: size,
1524 phase: BuildSharedWalPhase::NeedHeaderRead,
1525 })
1526 }
1527
1528 pub fn poll(&mut self) -> Result<IOResult<Arc<RwLock<WalFileShared>>>> {
1532 loop {
1533 match self.phase.clone() {
1534 BuildSharedWalPhase::NeedHeaderRead => {
1535 let reader = self
1536 .reader
1537 .clone()
1538 .expect("reader must exist outside the Done phase");
1539 let c = reader.read_header()?;
1540 self.phase = BuildSharedWalPhase::AwaitHeader(c);
1541 }
1542 BuildSharedWalPhase::AwaitHeader(c) => {
1543 if !c.succeeded() {
1544 io_yield_one!(c);
1545 }
1546 self.phase = BuildSharedWalPhase::ChunkLoop;
1547 }
1548 BuildSharedWalPhase::ChunkLoop => {
1549 let reader = self
1550 .reader
1551 .clone()
1552 .expect("reader must exist outside the Done phase");
1553 if reader.done.load(Ordering::Acquire) {
1554 self.phase = BuildSharedWalPhase::Done;
1555 continue;
1556 }
1557 let offset = reader.off_atomic.load(Ordering::Acquire);
1558 if offset >= self.file_size {
1559 reader.finalize_loading();
1560 self.phase = BuildSharedWalPhase::Done;
1561 continue;
1562 }
1563 let (_read_size, c) = reader.submit_one_chunk(offset)?;
1564 self.phase = BuildSharedWalPhase::AwaitChunk {
1565 completion: c,
1566 offset,
1567 };
1568 }
1569 BuildSharedWalPhase::AwaitChunk { completion, offset } => {
1570 if !completion.succeeded() {
1571 io_yield_one!(completion);
1572 }
1573 let reader = self
1574 .reader
1575 .clone()
1576 .expect("reader must exist outside the Done phase");
1577 let new_off = reader.off_atomic.load(Ordering::Acquire);
1578 if new_off <= offset {
1579 reader.finalize_loading();
1581 self.phase = BuildSharedWalPhase::Done;
1582 } else {
1583 self.phase = BuildSharedWalPhase::ChunkLoop;
1584 }
1585 }
1586 BuildSharedWalPhase::Done => {
1587 return Ok(IOResult::Done(self.wal_file_shared.clone()));
1588 }
1589 }
1590 }
1591 }
1592}
1593
1594pub fn build_shared_wal(
1597 file: &Arc<dyn File>,
1598 io: &Arc<dyn crate::IO>,
1599) -> Result<Arc<RwLock<WalFileShared>>> {
1600 let mut driver = BuildSharedWal::begin(file)?;
1601 io.block(|| driver.poll())
1602}
1603
1604pub(super) struct StreamingWalReader {
1605 file: Arc<dyn File>,
1606 wal_shared: Arc<RwLock<WalFileShared>>,
1607 header: Arc<SpinLock<WalHeader>>,
1608 file_size: u64,
1609 state: RwLock<StreamingState>,
1610 off_atomic: AtomicU64,
1611 page_atomic: AtomicU64,
1612 pub(super) done: AtomicBool,
1613}
1614
1615struct StreamingState {
1617 frame_idx: u64,
1618 cumulative_checksum: (u32, u32),
1619 last_valid_checksum: (u32, u32),
1621 last_valid_frame: u64,
1622 pending_frames: FxHashMap<u64, Vec<u64>>,
1623 page_size: usize,
1624 use_native_endian: bool,
1625 header_valid: bool,
1626}
1627
1628impl StreamingWalReader {
1629 fn new(
1630 file: Arc<dyn File>,
1631 wal_shared: Arc<RwLock<WalFileShared>>,
1632 header: Arc<SpinLock<WalHeader>>,
1633 file_size: u64,
1634 ) -> Self {
1635 Self {
1636 file,
1637 wal_shared,
1638 header,
1639 file_size,
1640 off_atomic: AtomicU64::new(0),
1641 page_atomic: AtomicU64::new(0),
1642 done: AtomicBool::new(false),
1643 state: RwLock::new(StreamingState {
1644 frame_idx: 1,
1645 cumulative_checksum: (0, 0),
1646 last_valid_checksum: (0, 0),
1647 last_valid_frame: 0,
1648 pending_frames: FxHashMap::default(),
1649 page_size: 0,
1650 use_native_endian: false,
1651 header_valid: false,
1652 }),
1653 }
1654 }
1655
1656 fn read_header(self: Arc<Self>) -> crate::Result<Completion> {
1657 let header_buf = Arc::new(Buffer::new_temporary(WAL_HEADER_SIZE));
1658 let reader = self.clone();
1659 let completion: Box<ReadComplete> = Box::new(move |res| {
1660 let _reader = reader.clone();
1661 _reader.handle_header_read(res);
1662 None
1663 });
1664 let c = Completion::new_read(header_buf, completion);
1665 self.file.pread(0, c)
1666 }
1667
1668 fn submit_one_chunk(self: Arc<Self>, offset: u64) -> crate::Result<(usize, Completion)> {
1669 let page_size = self.page_atomic.load(Ordering::Acquire) as usize;
1670 if page_size == 0 {
1671 return Err(crate::LimboError::InternalError(
1672 "page size not initialized".into(),
1673 ));
1674 }
1675 let frame_size = WAL_FRAME_HEADER_SIZE + page_size;
1676 if frame_size == 0 {
1677 return Err(crate::LimboError::InternalError(
1678 "invalid frame size".into(),
1679 ));
1680 }
1681 const BASE: usize = 16 * 1024 * 1024;
1682 let aligned = (BASE / frame_size) * frame_size;
1683 let read_size = aligned
1684 .max(frame_size)
1685 .min((self.file_size - offset) as usize);
1686 if read_size == 0 {
1687 return Ok((0, Completion::new_yield()));
1689 }
1690
1691 let buf = Arc::new(Buffer::new_temporary(read_size));
1692 let me = self.clone();
1693 let completion: Box<ReadComplete> = Box::new(move |res| {
1694 tracing::debug!("WAL chunk read complete");
1695 let reader = me.clone();
1696 reader.handle_chunk_read(res);
1697 None
1698 });
1699 let c = Completion::new_read(buf, completion);
1700 let guard = self.file.pread(offset, c)?;
1701 Ok((read_size, guard))
1702 }
1703
1704 fn handle_header_read(self: Arc<Self>, res: Result<(Arc<Buffer>, i32), CompletionError>) {
1705 let Ok((buf, bytes_read)) = res else {
1706 self.finalize_loading();
1707 return;
1708 };
1709 if bytes_read != WAL_HEADER_SIZE as i32 {
1710 self.finalize_loading();
1711 return;
1712 }
1713
1714 let (page_sz, c1, c2, use_native, ok) = {
1715 let mut h = self.header.lock();
1716 let s = buf.as_slice();
1717 h.magic = u32::from_be_bytes(s[0..4].try_into().unwrap());
1718 h.file_format = u32::from_be_bytes(s[4..8].try_into().unwrap());
1719 h.page_size = u32::from_be_bytes(s[8..12].try_into().unwrap());
1720 h.checkpoint_seq = u32::from_be_bytes(s[12..16].try_into().unwrap());
1721 h.salt_1 = u32::from_be_bytes(s[16..20].try_into().unwrap());
1722 h.salt_2 = u32::from_be_bytes(s[20..24].try_into().unwrap());
1723 h.checksum_1 = u32::from_be_bytes(s[24..28].try_into().unwrap());
1724 h.checksum_2 = u32::from_be_bytes(s[28..32].try_into().unwrap());
1725 tracing::debug!("WAL header: {:?}", *h);
1726
1727 let use_native = cfg!(target_endian = "big") == ((h.magic & 1) != 0);
1728 let calc = checksum_wal(&s[0..24], &h, (0, 0), use_native);
1729 (
1730 h.page_size,
1731 h.checksum_1,
1732 h.checksum_2,
1733 use_native,
1734 calc == (h.checksum_1, h.checksum_2),
1735 )
1736 };
1737 #[cfg(debug_assertions)]
1738 {
1739 let header = self.header.lock();
1740 tracing::debug!(
1741 "WAL_SCAN header page_size={} checkpoint_seq={} salts=({}, {}) checksum=({}, {}) use_native={} valid={}",
1742 page_sz,
1743 header.checkpoint_seq,
1744 header.salt_1,
1745 header.salt_2,
1746 c1,
1747 c2,
1748 use_native,
1749 ok
1750 );
1751 }
1752 if PageSize::new(page_sz).is_none() || !ok {
1753 self.finalize_loading();
1754 return;
1755 }
1756 {
1757 let mut st = self.state.write();
1758 st.page_size = page_sz as usize;
1759 st.use_native_endian = use_native;
1760 st.cumulative_checksum = (c1, c2);
1761 st.last_valid_checksum = (c1, c2);
1762 st.header_valid = true;
1763 }
1764 self.off_atomic
1765 .store(WAL_HEADER_SIZE as u64, Ordering::Release);
1766 self.page_atomic.store(page_sz as u64, Ordering::Release);
1767 }
1768
1769 fn handle_chunk_read(self: Arc<Self>, res: Result<(Arc<Buffer>, i32), CompletionError>) {
1770 let Ok((buf, bytes_read)) = res else {
1771 self.finalize_loading();
1772 return;
1773 };
1774 let buf_slice = &buf.as_slice()[..bytes_read as usize];
1775 let (header_copy, use_native) = {
1777 let st = self.state.read();
1778 let h = self.header.lock();
1779 (*h, st.use_native_endian)
1780 };
1781
1782 let consumed = self.process_frames(buf_slice, &header_copy, use_native);
1783 self.off_atomic.fetch_add(consumed as u64, Ordering::AcqRel);
1784 if consumed < buf_slice.len() || self.off_atomic.load(Ordering::Acquire) >= self.file_size {
1786 self.finalize_loading();
1787 }
1788 }
1789
1790 fn process_frames(&self, buf: &[u8], header: &WalHeader, use_native: bool) -> usize {
1792 let mut st = self.state.write();
1793 let page_size = st.page_size;
1794 let frame_size = WAL_FRAME_HEADER_SIZE + page_size;
1795 let mut pos = 0;
1796
1797 while pos + frame_size <= buf.len() {
1798 let fh = &buf[pos..pos + WAL_FRAME_HEADER_SIZE];
1799 let page = &buf[pos + WAL_FRAME_HEADER_SIZE..pos + frame_size];
1800
1801 let page_no = u32::from_be_bytes(fh[0..4].try_into().unwrap());
1802 let db_size = u32::from_be_bytes(fh[4..8].try_into().unwrap());
1803 let s1 = u32::from_be_bytes(fh[8..12].try_into().unwrap());
1804 let s2 = u32::from_be_bytes(fh[12..16].try_into().unwrap());
1805 let c1 = u32::from_be_bytes(fh[16..20].try_into().unwrap());
1806 let c2 = u32::from_be_bytes(fh[20..24].try_into().unwrap());
1807
1808 tracing::debug!("process_frames: page_no={page_no}, db_size={db_size}, s1={s1}, s2={s2}, c1={c1}, c2={c2}");
1809
1810 if page_no == 0 {
1811 tracing::debug!(
1812 "process_frames: unexpected page_no, stop reading WAL at initialization phase"
1813 );
1814 break;
1815 }
1816 if s1 != header.salt_1 || s2 != header.salt_2 {
1817 tracing::debug!(
1818 "WAL_SCAN stop: frame={} salt mismatch frame=({}, {}) header=({}, {})",
1819 st.frame_idx,
1820 s1,
1821 s2,
1822 header.salt_1,
1823 header.salt_2
1824 );
1825 tracing::debug!(
1826 "process_frames: salt mismatch, stop reading WAL at initialization phase"
1827 );
1828 break;
1829 }
1830
1831 let seed = checksum_wal(&fh[0..8], header, st.cumulative_checksum, use_native);
1832 let calc = checksum_wal(page, header, seed, use_native);
1833 if calc != (c1, c2) {
1834 tracing::debug!(
1835 " WAL_SCAN stop: process_frames, checksum mismatch, stop reading WAL at initialization phase: frame={} checksum mismatch calc=({},{}) file=({},{})",
1836 st.frame_idx,
1837 calc.0,
1838 calc.1,
1839 c1,
1840 c2
1841 );
1842 break;
1843 }
1844
1845 st.cumulative_checksum = calc;
1846 let frame_idx = st.frame_idx;
1847 st.pending_frames
1848 .entry(page_no as u64)
1849 .or_default()
1850 .push(frame_idx);
1851
1852 if db_size > 0 {
1853 st.last_valid_frame = st.frame_idx;
1854 st.last_valid_checksum = calc;
1855 tracing::debug!(
1856 "WAL_SCAN commit frame={} page_no={} db_size={}",
1857 st.frame_idx,
1858 page_no,
1859 db_size
1860 );
1861 self.flush_pending_frames(&mut st);
1862 }
1863 st.frame_idx += 1;
1864 pos += frame_size;
1865 }
1866 pos
1867 }
1868
1869 fn flush_pending_frames(&self, state: &mut StreamingState) {
1870 if state.pending_frames.is_empty() {
1871 return;
1872 }
1873 let wfs = self.wal_shared.read();
1874 let mut frame_cache = wfs.runtime.frame_cache.lock();
1875 for (page, mut frames) in state.pending_frames.drain() {
1876 frames.retain(|&f| f <= state.last_valid_frame);
1878 if !frames.is_empty() {
1879 frame_cache.entry(page).or_default().extend(frames);
1880 }
1881 }
1882 wfs.metadata
1883 .max_frame
1884 .store(state.last_valid_frame, Ordering::Release);
1885 wfs.runtime
1889 .frame_cache_high_water
1890 .fetch_max(state.last_valid_frame, Ordering::AcqRel);
1891 }
1892
1893 fn finalize_loading(&self) {
1895 let mut wfs = self.wal_shared.write();
1896 let st = self.state.read();
1897 tracing::debug!(
1898 "WAL_SCAN finalize last_valid_frame={} pending_pages={} header_valid={}",
1899 st.last_valid_frame,
1900 st.pending_frames.len(),
1901 st.header_valid
1902 );
1903
1904 let max_frame = st.last_valid_frame;
1905 if max_frame > 0 {
1906 let mut frame_cache = wfs.runtime.frame_cache.lock();
1907 for frames in frame_cache.values_mut() {
1908 frames.retain(|&f| f <= max_frame);
1909 }
1910 frame_cache.retain(|_, frames| !frames.is_empty());
1911 let header = wfs.metadata.wal_header.lock();
1912 wfs.runtime.overflow_fallback_coverage.lock().record(
1913 header.checkpoint_seq,
1914 header.salt_1,
1915 header.salt_2,
1916 max_frame,
1917 );
1918 } else {
1919 wfs.runtime.overflow_fallback_coverage.lock().clear();
1920 }
1921
1922 wfs.metadata.max_frame.store(max_frame, Ordering::SeqCst);
1923 wfs.metadata.last_checksum = st.last_valid_checksum;
1925 if st.header_valid {
1926 wfs.metadata.initialized.store(true, Ordering::SeqCst);
1927 }
1928 wfs.metadata.nbackfills.store(0, Ordering::SeqCst);
1929 wfs.metadata.loaded.store(true, Ordering::SeqCst);
1930
1931 self.done.store(true, Ordering::Release);
1932 tracing::debug!(
1933 "WAL loading complete: {} frames processed, last commit at frame {}",
1934 st.frame_idx - 1,
1935 max_frame
1936 );
1937 }
1938}
1939
1940pub fn begin_read_wal_frame_raw<F: File + ?Sized>(
1941 buffer_pool: &Arc<BufferPool>,
1942 io: &F,
1943 offset: u64,
1944 complete: Box<ReadComplete>,
1945) -> Result<Completion> {
1946 tracing::trace!("begin_read_wal_frame_raw(offset={})", offset);
1947 let buf = Arc::new(buffer_pool.get_wal_frame());
1948 let c = Completion::new_read(buf, complete);
1949 let c = io.pread(offset, c)?;
1950 Ok(c)
1951}
1952
1953pub fn begin_read_wal_frame<F: File + ?Sized>(
1954 io: &F,
1955 offset: u64,
1956 buffer_pool: Arc<BufferPool>,
1957 complete: Box<ReadComplete>,
1958 page_idx: usize,
1959 io_ctx: &IOContext,
1960) -> Result<Completion> {
1961 tracing::trace!(
1962 "begin_read_wal_frame(offset={}, page_idx={})",
1963 offset,
1964 page_idx
1965 );
1966 let buf = buffer_pool.get_page();
1967 let buf = Arc::new(buf);
1968
1969 match io_ctx.encryption_or_checksum() {
1970 EncryptionOrChecksum::Encryption(ctx) => {
1971 let encryption_ctx = ctx.clone();
1972 let original_complete = complete;
1973
1974 let decrypt_complete =
1975 Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {
1976 let Ok((encrypted_buf, bytes_read)) = res else {
1977 return original_complete(res);
1978 };
1979 turso_assert_greater_than!(
1980 bytes_read, 0,
1981 "expected to read data for encrypted page",
1982 { "page_idx": page_idx }
1983 );
1984 match encryption_ctx.decrypt_page(encrypted_buf.as_slice(), page_idx) {
1985 Ok(decrypted_data) => {
1986 encrypted_buf
1987 .as_mut_slice()
1988 .copy_from_slice(&decrypted_data);
1989 original_complete(Ok((encrypted_buf, bytes_read)))
1990 }
1991 Err(e) => {
1992 tracing::error!(
1993 "Failed to decrypt WAL frame data for page_idx={page_idx}: {e}"
1994 );
1995 let err = CompletionError::DecryptionError { page_idx };
1996 original_complete(Err(err));
1997 Some(err)
1998 }
1999 }
2000 });
2001
2002 let new_completion = Completion::new_read(buf, decrypt_complete);
2003 io.pread(offset, new_completion)
2004 }
2005 EncryptionOrChecksum::Checksum(ctx) => {
2006 let checksum_ctx = ctx.clone();
2007 let original_c = complete;
2008 let verify_complete =
2009 Box::new(move |res: Result<(Arc<Buffer>, i32), CompletionError>| {
2010 let Ok((buf, bytes_read)) = res else {
2011 return original_c(res);
2012 };
2013 if bytes_read <= 0 {
2014 tracing::trace!("Read page {page_idx} with {} bytes", bytes_read);
2015 return original_c(Ok((buf, bytes_read)));
2016 }
2017
2018 match checksum_ctx.verify_checksum(buf.as_mut_slice(), page_idx) {
2019 Ok(_) => original_c(Ok((buf, bytes_read))),
2020 Err(e) => {
2021 mark_unlikely();
2022 tracing::error!(
2023 "Failed to verify checksum for page_id={page_idx}: {e}"
2024 );
2025 original_c(Err(e));
2026 Some(e)
2027 }
2028 }
2029 });
2030 let c = Completion::new_read(buf, verify_complete);
2031 io.pread(offset, c)
2032 }
2033 EncryptionOrChecksum::None => {
2034 let c = Completion::new_read(buf, complete);
2035 io.pread(offset, c)
2036 }
2037 }
2038}
2039
2040pub fn parse_wal_frame_header(frame: &[u8]) -> (WalFrameHeader, &[u8]) {
2041 let page_number = u32::from_be_bytes(frame[0..4].try_into().unwrap());
2042 let db_size = u32::from_be_bytes(frame[4..8].try_into().unwrap());
2043 let salt_1 = u32::from_be_bytes(frame[8..12].try_into().unwrap());
2044 let salt_2 = u32::from_be_bytes(frame[12..16].try_into().unwrap());
2045 let checksum_1 = u32::from_be_bytes(frame[16..20].try_into().unwrap());
2046 let checksum_2 = u32::from_be_bytes(frame[20..24].try_into().unwrap());
2047 let header = WalFrameHeader {
2048 page_number,
2049 db_size,
2050 salt_1,
2051 salt_2,
2052 checksum_1,
2053 checksum_2,
2054 };
2055 let page = &frame[WAL_FRAME_HEADER_SIZE..];
2056 (header, page)
2057}
2058
2059pub fn prepare_wal_frame(
2060 buffer_pool: &Arc<BufferPool>,
2061 wal_header: &WalHeader,
2062 prev_checksums: (u32, u32),
2063 page_size: u32,
2064 page_number: u32,
2065 db_size: u32,
2066 page: &[u8],
2067) -> ((u32, u32), Arc<Buffer>) {
2068 tracing::trace!(page_number);
2069
2070 let buffer = buffer_pool.get_wal_frame();
2071 let frame = buffer.as_mut_slice();
2072 frame[WAL_FRAME_HEADER_SIZE..].copy_from_slice(page);
2073
2074 frame[0..4].copy_from_slice(&page_number.to_be_bytes());
2075 frame[4..8].copy_from_slice(&db_size.to_be_bytes());
2076 frame[8..12].copy_from_slice(&wal_header.salt_1.to_be_bytes());
2077 frame[12..16].copy_from_slice(&wal_header.salt_2.to_be_bytes());
2078
2079 let expects_be = wal_header.magic & 1;
2080 let use_native_endian = cfg!(target_endian = "big") as u32 == expects_be;
2081 let header_checksum = checksum_wal(&frame[0..8], wal_header, prev_checksums, use_native_endian);
2082 let final_checksum = checksum_wal(
2083 &frame[WAL_FRAME_HEADER_SIZE..WAL_FRAME_HEADER_SIZE + page_size as usize],
2084 wal_header,
2085 header_checksum,
2086 use_native_endian,
2087 );
2088 frame[16..20].copy_from_slice(&final_checksum.0.to_be_bytes());
2089 frame[20..24].copy_from_slice(&final_checksum.1.to_be_bytes());
2090
2091 (final_checksum, Arc::new(buffer))
2092}
2093
2094pub fn begin_write_wal_header<F: File + ?Sized>(io: &F, header: &WalHeader) -> Result<Completion> {
2095 tracing::trace!("begin_write_wal_header");
2096 let buffer = {
2097 let buffer = Buffer::new_temporary(WAL_HEADER_SIZE);
2098 let buf = buffer.as_mut_slice();
2099
2100 buf[0..4].copy_from_slice(&header.magic.to_be_bytes());
2101 buf[4..8].copy_from_slice(&header.file_format.to_be_bytes());
2102 buf[8..12].copy_from_slice(&header.page_size.to_be_bytes());
2103 buf[12..16].copy_from_slice(&header.checkpoint_seq.to_be_bytes());
2104 buf[16..20].copy_from_slice(&header.salt_1.to_be_bytes());
2105 buf[20..24].copy_from_slice(&header.salt_2.to_be_bytes());
2106 buf[24..28].copy_from_slice(&header.checksum_1.to_be_bytes());
2107 buf[28..32].copy_from_slice(&header.checksum_2.to_be_bytes());
2108
2109 #[allow(clippy::arc_with_non_send_sync)]
2110 Arc::new(buffer)
2111 };
2112
2113 let write_complete = move |res: Result<i32, CompletionError>| {
2114 let Ok(bytes_written) = res else {
2115 return;
2116 };
2117 turso_assert!(
2118 bytes_written == WAL_HEADER_SIZE as i32,
2119 "wal header wrote({bytes_written}) != expected({WAL_HEADER_SIZE})"
2120 );
2121 };
2122 #[allow(clippy::arc_with_non_send_sync)]
2123 let c = Completion::new_write(write_complete);
2124 let c = io.pwrite(0, buffer, c)?;
2125 Ok(c)
2126}
2127
2128#[inline]
2133pub fn payload_overflows(
2134 payload_size: usize,
2135 payload_overflow_threshold_max: usize,
2136 payload_overflow_threshold_min: usize,
2137 usable_size: usize,
2138) -> (bool, usize) {
2139 if payload_size <= payload_overflow_threshold_max {
2140 return (false, 0);
2141 }
2142
2143 let mut space_left = payload_overflow_threshold_min
2144 + (payload_size - payload_overflow_threshold_min) % (usable_size - 4);
2145 if space_left > payload_overflow_threshold_max {
2146 space_left = payload_overflow_threshold_min;
2147 }
2148 (true, space_left + 4)
2149}
2150
2151#[inline]
2170pub fn checksum_wal(
2171 buf: &[u8],
2172 _wal_header: &WalHeader,
2173 input: (u32, u32),
2174 native_endian: bool, ) -> (u32, u32) {
2176 turso_assert_eq!(buf.len() % 8, 0, "buffer must be a multiple of 8");
2177 let mut s0: u32 = input.0;
2178 let mut s1: u32 = input.1;
2179 let mut i = 0;
2180 if native_endian {
2181 while i < buf.len() {
2182 let v0 = u32::from_ne_bytes(buf[i..i + 4].try_into().unwrap());
2183 let v1 = u32::from_ne_bytes(buf[i + 4..i + 8].try_into().unwrap());
2184 s0 = s0.wrapping_add(v0.wrapping_add(s1));
2185 s1 = s1.wrapping_add(v1.wrapping_add(s0));
2186 i += 8;
2187 }
2188 } else {
2189 while i < buf.len() {
2190 let v0 = u32::from_ne_bytes(buf[i..i + 4].try_into().unwrap()).swap_bytes();
2191 let v1 = u32::from_ne_bytes(buf[i + 4..i + 8].try_into().unwrap()).swap_bytes();
2192 s0 = s0.wrapping_add(v0.wrapping_add(s1));
2193 s1 = s1.wrapping_add(v1.wrapping_add(s0));
2194 i += 8;
2195 }
2196 }
2197 (s0, s1)
2198}
2199
2200impl WalHeader {
2201 pub fn as_bytes(&self) -> &[u8] {
2202 unsafe { std::mem::transmute::<&WalHeader, &[u8; size_of::<WalHeader>()]>(self) }
2203 }
2204}
2205
2206#[inline]
2207pub fn read_u32(buf: &[u8], pos: usize) -> u32 {
2208 u32::from_be_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]])
2209}
2210
2211#[cfg(clt_turso_tests)]
2212mod tests {
2213 use crate::Value;
2214
2215 use super::*;
2216 use rstest::rstest;
2217
2218 #[rstest]
2219 #[case(&[], SerialType::null(), Value::Null)]
2220 #[case(&[255], SerialType::i8(), Value::from_i64(-1))]
2221 #[case(&[0x12, 0x34], SerialType::i16(), Value::from_i64(0x1234))]
2222 #[case(&[0xFE], SerialType::i8(), Value::from_i64(-2))]
2223 #[case(&[0x12, 0x34, 0x56], SerialType::i24(), Value::from_i64(0x123456))]
2224 #[case(&[0x12, 0x34, 0x56, 0x78], SerialType::i32(), Value::from_i64(0x12345678))]
2225 #[case(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], SerialType::i48(), Value::from_i64(0x123456789ABC))]
2226 #[case(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF], SerialType::i64(), Value::from_i64(0x123456789ABCDEFF))]
2227 #[case(&[0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18], SerialType::f64(), Value::from_f64(std::f64::consts::PI))]
2228 #[case(&[1, 2], SerialType::const_int0(), Value::from_i64(0))]
2229 #[case(&[65, 66], SerialType::const_int1(), Value::from_i64(1))]
2230 #[case(&[1, 2, 3], SerialType::blob(3), Value::Blob(vec![1, 2, 3]))]
2231 #[case(&[], SerialType::blob(0), Value::Blob(vec![]))] #[case(&[65, 66, 67], SerialType::text(3), Value::build_text("ABC"))]
2233 #[case(&[0x80], SerialType::i8(), Value::from_i64(-128))]
2234 #[case(&[0x80, 0], SerialType::i16(), Value::from_i64(-32768))]
2235 #[case(&[0x80, 0, 0], SerialType::i24(), Value::from_i64(-8388608))]
2236 #[case(&[0x80, 0, 0, 0], SerialType::i32(), Value::from_i64(-2147483648))]
2237 #[case(&[0x80, 0, 0, 0, 0, 0], SerialType::i48(), Value::from_i64(-140737488355328))]
2238 #[case(&[0x80, 0, 0, 0, 0, 0, 0, 0], SerialType::i64(), Value::from_i64(-9223372036854775808))]
2239 #[case(&[0x7f], SerialType::i8(), Value::from_i64(127))]
2240 #[case(&[0x7f, 0xff], SerialType::i16(), Value::from_i64(32767))]
2241 #[case(&[0x7f, 0xff, 0xff], SerialType::i24(), Value::from_i64(8388607))]
2242 #[case(&[0x7f, 0xff, 0xff, 0xff], SerialType::i32(), Value::from_i64(2147483647))]
2243 #[case(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff], SerialType::i48(), Value::from_i64(140737488355327))]
2244 #[case(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], SerialType::i64(), Value::from_i64(9223372036854775807))]
2245 fn test_read_value(
2246 #[case] buf: &[u8],
2247 #[case] serial_type: SerialType,
2248 #[case] expected: Value,
2249 ) {
2250 let result = read_value(buf, serial_type).unwrap();
2251 assert_eq!(result.0.to_owned(), expected);
2252 }
2253
2254 #[test]
2255 fn test_serial_type_helpers() {
2256 assert_eq!(
2257 TryInto::<SerialType>::try_into(12u64).unwrap(),
2258 SerialType::blob(0)
2259 );
2260 assert_eq!(
2261 TryInto::<SerialType>::try_into(14u64).unwrap(),
2262 SerialType::blob(1)
2263 );
2264 assert_eq!(
2265 TryInto::<SerialType>::try_into(13u64).unwrap(),
2266 SerialType::text(0)
2267 );
2268 assert_eq!(
2269 TryInto::<SerialType>::try_into(15u64).unwrap(),
2270 SerialType::text(1)
2271 );
2272 assert_eq!(
2273 TryInto::<SerialType>::try_into(16u64).unwrap(),
2274 SerialType::blob(2)
2275 );
2276 assert_eq!(
2277 TryInto::<SerialType>::try_into(17u64).unwrap(),
2278 SerialType::text(2)
2279 );
2280 }
2281
2282 #[rstest]
2283 #[case(0, SerialType::null())]
2284 #[case(1, SerialType::i8())]
2285 #[case(2, SerialType::i16())]
2286 #[case(3, SerialType::i24())]
2287 #[case(4, SerialType::i32())]
2288 #[case(5, SerialType::i48())]
2289 #[case(6, SerialType::i64())]
2290 #[case(7, SerialType::f64())]
2291 #[case(8, SerialType::const_int0())]
2292 #[case(9, SerialType::const_int1())]
2293 #[case(12, SerialType::blob(0))]
2294 #[case(13, SerialType::text(0))]
2295 #[case(14, SerialType::blob(1))]
2296 #[case(15, SerialType::text(1))]
2297 fn test_parse_serial_type(#[case] input: u64, #[case] expected: SerialType) {
2298 let result = SerialType::try_from(input).unwrap();
2299 assert_eq!(result, expected);
2300 }
2301
2302 #[test]
2303 fn test_validate_serial_type() {
2304 for i in 0..=9 {
2305 let result = validate_serial_type(i);
2306 assert!(result.is_ok());
2307 }
2308 for i in 10..=11 {
2309 let result = validate_serial_type(i);
2310 assert!(result.is_err());
2311 }
2312 for i in 12..=1000 {
2313 let result = validate_serial_type(i);
2314 assert!(result.is_ok());
2315 }
2316 }
2317
2318 #[rstest]
2319 #[case(&[])] #[case(&[0x80])] #[case(&[0x80, 0x80])] #[case(&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80])] #[case(&[0x80; 9])] fn test_read_varint_malformed_inputs(#[case] buf: &[u8]) {
2325 assert!(read_varint(buf).is_err());
2326 }
2327
2328 #[test]
2329 fn streaming_reader_ignores_uncommitted_checksums() {
2330 let io: Arc<dyn crate::IO> = Arc::new(crate::MemoryIO::new());
2331 let file = io
2332 .open_file("streaming-reader-wal", crate::OpenFlags::Create, false)
2333 .unwrap();
2334
2335 let page_size: usize = 1024;
2336 let buffer_pool = BufferPool::begin_init(&io, BufferPool::TEST_ARENA_SIZE);
2337 buffer_pool
2338 .finalize_with_page_size(page_size)
2339 .expect("initialize buffer pool");
2340
2341 let mut wal_header = WalHeader {
2342 magic: WAL_MAGIC_LE,
2343 file_format: 3007000,
2344 page_size: page_size as u32,
2345 checkpoint_seq: 0,
2346 salt_1: 0x1234_5678,
2347 salt_2: 0x9abc_def0,
2348 checksum_1: 0,
2349 checksum_2: 0,
2350 };
2351 let header_prefix = &wal_header.as_bytes()[..WAL_HEADER_SIZE - 8];
2352 let use_native = (wal_header.magic & 1) != 0;
2353 let (c1, c2) = checksum_wal(header_prefix, &wal_header, (0, 0), use_native);
2354 wal_header.checksum_1 = c1;
2355 wal_header.checksum_2 = c2;
2356 io.wait_for_completion(begin_write_wal_header(file.as_ref(), &wal_header).unwrap())
2357 .unwrap();
2358
2359 let page = vec![0xAB; page_size];
2360 let frame_size = WAL_FRAME_HEADER_SIZE + page_size;
2361 let mut offset = WAL_HEADER_SIZE as u64;
2362
2363 let (commit_checksum, commit_frame) = prepare_wal_frame(
2364 &buffer_pool,
2365 &wal_header,
2366 (wal_header.checksum_1, wal_header.checksum_2),
2367 wal_header.page_size,
2368 1,
2369 1,
2370 &page,
2371 );
2372 let commit_frame_clone = commit_frame.clone();
2373 let c = file
2374 .pwrite(
2375 offset,
2376 commit_frame,
2377 Completion::new_write(move |res| {
2378 assert_eq!(res.unwrap() as usize, frame_size);
2379 let _keep = commit_frame_clone.clone();
2380 }),
2381 )
2382 .unwrap();
2383 io.wait_for_completion(c).unwrap();
2384 offset += frame_size as u64;
2385
2386 let (after_frame2_checksum, frame2) = prepare_wal_frame(
2387 &buffer_pool,
2388 &wal_header,
2389 commit_checksum,
2390 wal_header.page_size,
2391 2,
2392 0,
2393 &page,
2394 );
2395 let frame2_clone = frame2.clone();
2396 let c = file
2397 .pwrite(
2398 offset,
2399 frame2,
2400 Completion::new_write(move |res| {
2401 assert_eq!(res.unwrap() as usize, frame_size);
2402 let _keep = frame2_clone.clone();
2403 }),
2404 )
2405 .unwrap();
2406 io.wait_for_completion(c).unwrap();
2407 offset += frame_size as u64;
2408
2409 let (after_frame3_checksum, frame3) = prepare_wal_frame(
2410 &buffer_pool,
2411 &wal_header,
2412 after_frame2_checksum,
2413 wal_header.page_size,
2414 3,
2415 0,
2416 &page,
2417 );
2418 let frame3_clone = frame3.clone();
2419 let c = file
2420 .pwrite(
2421 offset,
2422 frame3,
2423 Completion::new_write(move |res| {
2424 assert_eq!(res.unwrap() as usize, frame_size);
2425 let _keep = frame3_clone.clone();
2426 }),
2427 )
2428 .unwrap();
2429 io.wait_for_completion(c).unwrap();
2430
2431 let shared = build_shared_wal(&file, &io).unwrap();
2432 let guard = shared.read();
2433 assert_eq!(guard.metadata.max_frame.load(Ordering::Acquire), 1);
2434 assert_eq!(guard.metadata.last_checksum, commit_checksum);
2435
2436 assert_ne!(guard.metadata.last_checksum, after_frame3_checksum);
2438
2439 let frame_cache = guard.runtime.frame_cache.lock();
2440 assert_eq!(frame_cache.get(&1), Some(&vec![1u64]));
2441 assert!(frame_cache.get(&2).is_none());
2442 }
2443
2444 #[quickcheck_macros::quickcheck]
2445 fn varint_len_matches_write_varint(value: u64) -> bool {
2446 let mut buf = [0u8; 9];
2447 let written = write_varint(&mut buf, value);
2448 varint_len(value) == written
2449 }
2450}