Skip to main content

clt_database/storage/
sqlite3_ondisk.rs

1//! SQLite on-disk file format.
2//!
3//! SQLite stores data in a single database file, which is divided into fixed-size
4//! pages:
5//!
6//! ```text
7//! +----------+----------+----------+-----------------------------+----------+
8//! |          |          |          |                             |          |
9//! |  Page 1  |  Page 2  |  Page 3  |           ...               |  Page N  |
10//! |          |          |          |                             |          |
11//! +----------+----------+----------+-----------------------------+----------+
12//! ```
13//!
14//! The first page is special because it contains a 100 byte header at the beginning.
15//!
16//! Each page consists of a page header and N cells, which contain the records.
17//!
18//! ```text
19//! +-----------------+----------------+---------------------+----------------+
20//! |                 |                |                     |                |
21//! |   Page header   |  Cell pointer  |     Unallocated     |  Cell content  |
22//! | (8 or 12 bytes) |     array      |        space        |      area      |
23//! |                 |                |                     |                |
24//! +-----------------+----------------+---------------------+----------------+
25//! ```
26//!
27//! The write-ahead log (WAL) is a separate file that contains the physical
28//! log of changes to a database file. The file starts with a WAL header and
29//! is followed by a sequence of WAL frames, which are database pages with
30//! additional metadata.
31//!
32//! ```text
33//! +-----------------+-----------------+-----------------+-----------------+
34//! |                 |                 |                 |                 |
35//! |    WAL header   |    WAL frame 1  |    WAL frame 2  |    WAL frame N  |
36//! |                 |                 |                 |                 |
37//! +-----------------+-----------------+-----------------+-----------------+
38//! ```
39//!
40//! For more information, see the SQLite file format specification:
41//!
42//! https://www.sqlite.org/fileformat.html
43
44#![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
77/// The minimum size of a cell in bytes.
78pub 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
85// Freelist trunk page layout:
86// - Bytes 0-3: Page number of next freelist trunk page (0 if none)
87// - Bytes 4-7: Number of leaf page pointers on this trunk page
88// - Bytes 8+: Array of 4-byte leaf page pointers
89pub 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)]
97/// Read/Write file format version.
98pub 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    /// Interpret a user-provided u32 as either a valid page size or None.
106    pub const fn new(size: u32) -> Option<Self> {
107        if size < PageSize::MIN || size > PageSize::MAX {
108            return None;
109        }
110
111        // Page size must be a power of two.
112        if size.count_ones() != 1 {
113            return None;
114        }
115
116        if size == PageSize::MAX {
117            // Internally, the value 1 represents 65536, since the on-disk value of the page size in the DB header is 2 bytes.
118            return Some(Self(U16BE::new(1)));
119        }
120
121        Some(Self(U16BE::new(size as u16)))
122    }
123
124    /// Interpret a u16 on disk (DB file header) as either a valid page size or
125    /// return a corrupt error.
126    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    /// Get the raw u16 value stored internally
147    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)]
160/// Read/Write file format version.
161pub struct CacheSize(I32BE);
162
163impl CacheSize {
164    // The negative value means that we store the amount of pages a XKiB of memory can hold.
165    // We can calculate "real" cache size by diving by page size.
166    pub const DEFAULT: i32 = -2000;
167
168    // Minimum number of pages that cache can hold.
169    pub const MIN: i64 = super::page_cache::MINIMUM_PAGE_CACHE_SIZE_IN_PAGES as i64;
170
171    // SQLite uses this value as threshold for maximum cache size
172    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/// Read/Write file format version.
196#[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/// Raw version byte for use in DatabaseHeader where Pod is required.
235/// Use `Version::try_from(raw.0)` to convert to the validated enum.
236#[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)]
263/// Text encoding.
264pub struct TextEncoding(U32BE);
265
266impl TextEncoding {
267    #![allow(non_upper_case_globals)]
268    // SQLite doesn't write the text encoding bytes until the first table is written, so when
269    // opening an empty SQLite file, the encoding bytes will be 0. SQLite considers this to mean UTF-8.
270    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)]
311/// Database Header Format
312pub struct DatabaseHeader {
313    /// b"SQLite format 3\0"
314    pub magic: [u8; 16],
315    /// Page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
316    pub page_size: PageSize,
317    /// File format write version. 1 for legacy; 2 for WAL.
318    pub write_version: RawVersion,
319    /// File format read version. 1 for legacy; 2 for WAL.
320    pub read_version: RawVersion,
321    /// Bytes of unused "reserved" space at the end of each page. Usually 0.
322    pub reserved_space: u8,
323    /// Maximum embedded payload fraction. Must be 64.
324    pub max_embed_frac: u8,
325    /// Minimum embedded payload fraction. Must be 32.
326    pub min_embed_frac: u8,
327    /// Leaf payload fraction. Must be 32.
328    pub leaf_frac: u8,
329    /// File change counter.
330    pub change_counter: U32BE,
331    /// Size of the database file in pages. The "in-header database size".
332    pub database_size: U32BE,
333    /// Page number of the first freelist trunk page.
334    pub freelist_trunk_page: U32BE,
335    /// Total number of freelist pages.
336    pub freelist_pages: U32BE,
337    /// The schema cookie.
338    pub schema_cookie: U32BE,
339    /// The schema format number. Supported schema formats are 1, 2, 3, and 4.
340    pub schema_format: U32BE,
341    /// Default page cache size.
342    pub default_page_cache_size: CacheSize,
343    /// The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise.
344    pub vacuum_mode_largest_root_page: U32BE,
345    /// Text encoding.
346    pub text_encoding: TextEncoding,
347    /// The "user version" as read and set by the user_version pragma.
348    pub user_version: I32BE,
349    /// True (non-zero) for incremental-vacuum mode. False (zero) otherwise.
350    pub incremental_vacuum_enabled: U32BE,
351    /// The "Application ID" set by PRAGMA application_id.
352    pub application_id: I32BE,
353    /// Reserved for expansion. Must be zero.
354    _padding: [u8; 20],
355    /// The version-valid-for number.
356    pub version_valid_for: U32BE,
357    /// SQLITE_VERSION_NUMBER
358    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), // latest format, new sqlite3 databases use this format
391            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;
406// magic is a single number represented as WAL_MAGIC_LE but the big endian
407// counterpart is just the same number with LSB set to 1.
408pub const WAL_MAGIC_LE: u32 = 0x377f0682;
409pub const WAL_MAGIC_BE: u32 = 0x377f0683;
410
411/// The Write-Ahead Log (WAL) header.
412/// The first 32 bytes of a WAL file comprise the WAL header.
413/// The WAL header is divided into the following fields stored in big-endian order.
414#[derive(Debug, Clone, Copy)]
415#[repr(C)] // This helps with encoding because rust does not respect the order in structs, so in
416           // this case we want to keep the order
417pub struct WalHeader {
418    /// Magic number. 0x377f0682 or 0x377f0683
419    /// If the LSB is 0, checksums are native byte order, else checksums are serialized
420    pub magic: u32,
421
422    /// WAL format version. Currently 3007000
423    pub file_format: u32,
424
425    /// Database page size in bytes. Power of two between 512 and 65536 inclusive
426    pub page_size: u32,
427
428    /// Checkpoint sequence number. Increases with each checkpoint
429    pub checkpoint_seq: u32,
430
431    /// Random value used for the first salt in checksum calculations
432    /// TODO: Incremented with each checkpoint
433    pub salt_1: u32,
434
435    /// Random value used for the second salt in checksum calculations.
436    /// TODO: A different random value for each checkpoint
437    pub salt_2: u32,
438
439    /// First checksum value in the wal-header
440    pub checksum_1: u32,
441
442    /// Second checksum value in the wal-header
443    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, // Signifies WAL header that is not persistent on disk yet.
457            checkpoint_seq: 0, // TODO implement sequence number
458            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/// Immediately following the wal-header are zero or more frames.
473/// Each frame consists of a 24-byte frame-header followed by <page-size> bytes of page data.
474/// The frame-header is six big-endian 32-bit unsigned integer values, as follows:
475#[allow(dead_code)]
476#[derive(Debug, Default, Copy, Clone)]
477pub struct WalFrameHeader {
478    /// Page number
479    pub(crate) page_number: u32,
480
481    /// For commit records, the size of the database file in pages after the commit.
482    /// For all other records, zero.
483    pub(crate) db_size: u32,
484
485    /// Salt-1 copied from the WAL header
486    pub(crate) salt_1: u32,
487
488    /// Salt-2 copied from the WAL header
489    pub(crate) salt_2: u32,
490
491    /// Checksum-1: Cumulative checksum up through and including this page
492    pub(crate) checksum_1: u32,
493
494    /// Checksum-2: Second half of the cumulative checksum
495    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/// Send read request for DB page read to the IO
546/// if allow_empty_read is set, than empty read will be raise error for the page, but will not panic
547#[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; // IO error already captured in completion
564        };
565        let buf_len = buf.len();
566        // Handle truncated database files: if we read fewer bytes than expected
567        // (and it's not an intentional empty read), return a ShortRead error.
568        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        // we set the wal tag only when reading page from log, or in allocate_page,
611        // we clear it here for safety in case page is being re-loaded.
612        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)]
648/// Write a batch of pages to the database file.
649///
650/// we have a batch of pages to write, lets say the following:
651/// (they are already sorted by id thanks to BTreeMap)
652/// [1,2,3,6,7,9,10,11,12]
653//
654/// we want to collect this into runs of:
655/// [1,2,3], [6,7], [9,10,11,12]
656/// and submit each run as a `writev` call,
657/// for 3 total syscalls instead of 9.
658pub 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            // Record error/mismatch, but always resolve the batch progress.
712            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            // we have to decrement runs_left on both paths
727            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                // We failed to submit this run at all. Mark batch failed+done and cancel already-submitted.
741                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    /// Payload of cell, if it overflows it won't include overflowed payload.
791    pub payload: &'static [u8],
792    /// This is the complete payload size including overflow pages.
793    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    /// This is the complete payload size including overflow pages.
802    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    /// This is the complete payload size including overflow pages.
810    pub payload_size: u64,
811    pub first_overflow_page: Option<u32>,
812}
813
814/// read_btree_cell contructs a BTreeCell which is basically a wrapper around pointer to the payload of a cell.
815/// buffer input "page" is static because we want the cell to point to the data in the page in case it has any payload.
816pub 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/// read_payload takes in the unread bytearray with the payload size
932/// and returns the payload on the page, and optionally the first overflow page number.
933#[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    // We will let overflow be constructed back if needed or requested.
940    if payload_size <= cell_len {
941        // fit within 1 page
942        Ok((&unread[..payload_size], None))
943    } else {
944        // overflow
945        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/// Reads a value that might reference the buffer it is reading from. Be sure to store RefValue with the buffer
971/// always.
972#[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            // SAFETY: SerialTypeKind is Text so this buffer is a valid string
1092            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                // Blob
1201                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                // Text
1210                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                // SAFETY: SerialTypeKind is Text so this buffer is a valid string
1220                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/// Reads varint integer from the buffer.
1302/// This function is similar to `sqlite3GetVarint32`
1303#[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            // Values requiring 9 bytes must have non-zero in the top 8 bits (value >= 1<<56).
1323            // Since the final value is `(v<<8) + c`, the top 8 bits (v >> 48) must not be 0.
1324            // If those are zero, this should be treated as corrupt.
1325            // Perf? the comparison + branching happens only in parsing 9-byte varint which is rare.
1326            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)]
1340/// Reads a varint from the buffer, returning None if more data is needed.
1341pub 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/// Compute the length of a varint encoding for a given u64 value.
1363///
1364/// SQLite varint: bytes 1-8 each carry 7 payload bits (56 total).
1365/// The optional 9th byte carries a full 8 bits (no continuation bit),
1366/// giving 64 bits total.  So values needing >56 bits always take 9 bytes.
1367#[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
1424/// Stream through frames in chunks, building frame_cache incrementally
1425/// Track last valid commit frame for consistency
1426/// Non-blocking driver for WAL recovery on open.
1427///
1428/// Created by [`BuildSharedWal::begin`] (which performs only synchronous
1429/// setup and may complete immediately for an empty/headerless WAL), then
1430/// driven via [`BuildSharedWal::poll`] until it returns `Done`. All recovery
1431/// state lives in the [`StreamingWalReader`] (atomics + `RwLock<StreamingState>`)
1432/// and is updated by the read completions' callbacks, so the only state this
1433/// driver tracks is which phase/completion it's waiting on.
1434pub 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    /// Issue the WAL header read.
1444    NeedHeaderRead,
1445    /// Waiting on the header read completion.
1446    AwaitHeader(Completion),
1447    /// Decide whether to read the next chunk or finalize.
1448    ChunkLoop,
1449    /// Waiting on a chunk read that began at `offset`.
1450    AwaitChunk { completion: Completion, offset: u64 },
1451    /// Recovery complete.
1452    Done,
1453}
1454
1455impl BuildSharedWal {
1456    /// Synchronous setup: read the file size, build the (initially unloaded)
1457    /// `WalFileShared`, and decide the starting phase. For a WAL smaller than
1458    /// the header it marks the shared state loaded and starts in `Done`.
1459    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    /// Drive the recovery state machine. Yields the in-flight read completion
1529    /// when it must wait; returns `Done(wal_file_shared)` once the full WAL
1530    /// has been scanned (or recovery short-circuited).
1531    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                        // No forward progress — treat as end of valid log.
1580                        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
1594/// Blocking shim over [`BuildSharedWal`]. Retained for the unit test and any
1595/// caller not yet lifted to drive the recovery state machine directly.
1596pub 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
1615/// Mutable state for streaming reader
1616struct StreamingState {
1617    frame_idx: u64,
1618    cumulative_checksum: (u32, u32),
1619    /// checksum of the last valid commit frame
1620    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            // end-of-file; let caller finalize
1688            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        // Snapshot salts/endianness once to avoid per-frame header locks
1776        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 we didn’t consume the full chunk, we hit a stop condition
1785        if consumed < buf_slice.len() || self.off_atomic.load(Ordering::Acquire) >= self.file_size {
1786            self.finalize_loading();
1787        }
1788    }
1789
1790    // Processes frames from a buffer, returns bytes processed
1791    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            // Only include frames up to last valid commit
1877            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        // Recovery populates `frame_cache` directly (not via `cache_frame`), so
1886        // seed the high-water with the recovered frames; otherwise the first
1887        // post-recovery rewind/slot-reuse could go undetected.
1888        wfs.runtime
1889            .frame_cache_high_water
1890            .fetch_max(state.last_valid_frame, Ordering::AcqRel);
1891    }
1892
1893    /// Finalizes the loading process
1894    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        // use checksum of last valid commit frame, not necessarily the last frame
1924        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/// Checks if payload will overflow a cell based on the maximum allowed size.
2129/// It will return the min size that will be stored in that case,
2130/// including overflow pointer
2131/// see e.g. https://github.com/sqlite/sqlite/blob/9591d3fe93936533c8c3b0dc4d025ac999539e11/src/dbstat.c#L371
2132#[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/// The checksum is computed by interpreting the input as an even number of unsigned 32-bit integers: x(0) through x(N).
2152/// The 32-bit integers are big-endian if the magic number in the first 4 bytes of the WAL header is 0x377f0683
2153/// and the integers are little-endian if the magic number is 0x377f0682.
2154/// The checksum values are always stored in the frame header in a big-endian format regardless of which byte order is used to compute the checksum.
2155///
2156/// The checksum algorithm only works for content which is a multiple of 8 bytes in length.
2157/// In other words, if the inputs are x(0) through x(N) then N must be odd.
2158/// The checksum algorithm is as follows:
2159///
2160/// s0 = s1 = 0
2161/// for i from 0 to n-1 step 2:
2162///    s0 += x(i) + s1;
2163///    s1 += x(i+1) + s0;
2164/// endfor
2165///
2166/// The outputs s0 and s1 are both weighted checksums using Fibonacci weights in reverse order.
2167/// (The largest Fibonacci weight occurs on the first element of the sequence being summed.)
2168/// The s1 value spans all 32-bit integer terms of the sequence whereas s0 omits the final term.
2169#[inline]
2170pub fn checksum_wal(
2171    buf: &[u8],
2172    _wal_header: &WalHeader,
2173    input: (u32, u32),
2174    native_endian: bool, // Sqlite interprets big endian as "native"
2175) -> (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![]))] // empty blob
2232    #[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(&[])] // empty buffer
2320    #[case(&[0x80])] // truncated 1-byte with continuation
2321    #[case(&[0x80, 0x80])] // truncated 2-byte
2322    #[case(&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80])] // 9-byte truncated to 8
2323    #[case(&[0x80; 9])] // bits set without end
2324    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        // checksum should only include committed frame.
2437        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}