tursodatabase/turso · error · anyhow::Error

payload start out of bounds on page

Error message

payload start out of bounds on page {root_page}

What it means

The generator reads the raw bytes of a database page to locate the first record's payload, and validates that the computed payload offset lies within the page bytes. If the offset is beyond the page size, the fixture's expected page layout does not match the actual file, so it refuses to patch and reports the offending root page.

Solutions

  1. Verify the `root_page` matches the table's actual root page in the current schema.
  2. Ensure the `page_size` argument matches the database's PRAGMA page_size.
  3. Confirm the page is a table b-tree leaf (0x0D) containing the first row before patching.

Example fix

// before
let root_page = 2; // stale after schema change
// after
let root_page = db_first_table_root_page(&db_path, "t")?; // look up dynamically
Defensive patterns

Strategy: validation

Validate before calling

let page = read_page(&db_path, root_page, page_size)?;
assert_eq!(page[0], 0x0D, "page {root_page} must be a table leaf");
assert!(payload_start < page.len(), "payload_start {payload_start} out of bounds");

Prevention

When it happens

Trigger: `patch_first_row_serial_type` computes `payload_start` from the page's cell pointer and cell header, and it exceeds `bytes.len()` — usually because the wrong `root_page` or `page_size` was passed, or the page is not a table leaf page.

Common situations: Fixture schema changed (row moved to a different root page), wrong page_size argument, or pointing the patcher at an interior/overflow page instead of a leaf containing the first row.

Related errors


AI-assisted analysis of tursodatabase/turso@8d4a589f8d (2026-09-20). Data as JSON: /api/errors/5a98d67be8e5056a. Report an issue: GitHub.

Appendix: source

Thrown at testing/sqltest/src/generator/mod.rs:1070

    let first_cell_ptr =
        u16::from_be_bytes([bytes[ptr_array_start], bytes[ptr_array_start + 1]]) as usize;
    let cell_start = page_start + first_cell_ptr;
    anyhow::ensure!(
        cell_start < bytes.len(),
        "cell pointer out of bounds on page {root_page}"
    );

    let (_, payload_varint_len) = parse_sqlite_varint(&bytes, cell_start)?;
    let (_, rowid_varint_len) = parse_sqlite_varint(&bytes, cell_start + payload_varint_len)?;
    let payload_start = cell_start + payload_varint_len + rowid_varint_len;
    anyhow::ensure!(
        payload_start < bytes.len(),
        "payload start out of bounds on page {root_page}"
    );

    let (header_size, header_size_varint_len) = parse_sqlite_varint(&bytes, payload_start)?;
    let header_end = payload_start + header_size as usize;
    anyhow::ensure!(
        header_end <= bytes.len(),
        "record header out of bounds on page {root_page}"
    );

    let mut serial_offset = payload_start + header_size_varint_len;
    for _ in 0..patch.column_index {
        let (_, serial_len) = parse_sqlite_varint(&bytes, serial_offset)?;
        serial_offset += serial_len;
    }
    anyhow::ensure!(
        serial_offset < header_end,
        "record has no column {} on page {root_page}",
        patch.column_index
    );
    anyhow::ensure!(
        bytes[serial_offset] == patch.expected_serial_type,
        "unexpected serial type {} for column {} of the fixture row, expected {}",
        bytes[serial_offset],

View on GitHub (pinned to 8d4a589f8d)