tursodatabase/turso · error · anyhow::Error

record has no column

Error message

record has no column {} on page {root_page}

What it means

While walking the record header serial-type varints to reach the target column, the generator checks that the offset stops before the header end. If the header has fewer serial types than `patch.column_index`, the record has no such column and the fixture patch cannot be applied.

Solutions

  1. Update `patch.column_index` to match the current table schema column ordering.
  2. Re-generate the fixture so the schema and the patch agree.
  3. Count stored columns (excluding VIRTUAL generated columns) before choosing column_index.

Example fix

// before
let patch = FirstRowSerialTypePatch { column_index: 5, .. };
// after
let patch = FirstRowSerialTypePatch { column_index: 2, .. }; // matches current schema
Defensive patterns

Strategy: validation

Validate before calling

let stored_columns = count_record_serial_types(&page, payload_start)?;
assert!(patch.column_index < stored_columns, "column_index {} >= stored columns {}", patch.column_index, stored_columns);

Prevention

When it happens

Trigger: `patch_first_row_serial_type` is called with a `column_index` equal to or greater than the number of columns in the first row's record (e.g. patching column 5 of a 3-column table).

Common situations: The fixture schema was changed (columns added/removed/generated columns) but the patch's `column_index` wasn't updated, or the patch targets a generated-column variant with fewer stored columns.

Related errors


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

Appendix: source

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

    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],
        patch.column_index,
        patch.expected_serial_type
    );
    bytes[serial_offset] = patch.new_serial_type;

    std::fs::write(db_path, bytes)
        .with_context(|| format!("failed to write fixture '{}'", db_path.display()))?;
    Ok(())
}

async fn generate_non_unique_index_entry_fixture(db_path: &Path) -> Result<()> {
    clear_existing_db_and_sidecars(db_path)?;

    let db_path_str = db_path.to_string_lossy().to_string();
    let db = Builder::new_local(&db_path_str)

View on GitHub (pinned to 8d4a589f8d)