tursodatabase/turso · error · anyhow::Error
serial types and do not store the same number of bytes
Error message
serial types {} and {} do not store the same number of bytes What it means
When the sqltest fixture generator patches the first row's serial type in a raw database page, it requires that the old and new serial types encode the same payload size, because the patch is an in-place byte rewrite that cannot move data. Mismatched sizes would corrupt the record layout, so the generator rejects the patch up front.
Solutions
- Choose a `new_serial_type` with the same payload length as the original (same-size types: 0/8/9/12/13 are 0 bytes; 1/2/3/4/5/6 differ; 7 is 8 bytes).
- Verify with `sqlite_serial_type_payload_len` for both types before patching.
- If a different size is truly needed, write a full record rewrite instead of an in-place serial-type patch.
Example fix
// before patch.expected_serial_type = 1; // 1 byte patch.new_serial_type = 4; // 4 bytes -> rejected // after patch.expected_serial_type = 1; // 1 byte patch.new_serial_type = 2; // also 1 byte... use matching sizes, e.g. 1 -> 2
Defensive patterns
Strategy: validation
Validate before calling
use sqlite_serial_type_payload_len;
assert_eq!(
sqlite_serial_type_payload_len(u64::from(patch.expected_serial_type)),
sqlite_serial_type_payload_len(u64::from(patch.new_serial_type)),
"patch must replace a serial type with same payload size"
); Prevention
- Keep a table of serial-type payload sizes handy when writing patches
- Prefer same-width types (1<->2<->3<->4<->5<->6 differ; group 1|2 with same byte width per size class)
- Add the size check to a shared patch-builder helper
When it happens
Trigger: Calling `patch_first_row_serial_type` with a `FirstRowSerialTypePatch` whose `expected_serial_type` and `new_serial_type` have different payload lengths (e.g. replacing serial type 1 / int8 with serial type 4 / int32), via `generate_not_null_violation_fixture`, `generate_gencol_not_null_violation_fixture`, or `generate_strict_fixture`.
Common situations: Editing fixture definitions and choosing a replacement serial type of a different width, or hand-crafting a corrupt-fixture patch for a new test case.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- payload start out of bounds on page
- record has no column
- record header out of bounds on page
- unexpected serial type
- Too many restrictions specified for collection
AI-assisted analysis of tursodatabase/turso@8d4a589f8d (2026-09-20).
Data as JSON: /api/errors/bd2b9409bd22d5d4.
Report an issue: GitHub.
Appendix: source
Thrown at testing/sqltest/src/generator/mod.rs:1029
let table_root_page = get_root_page(&conn, "t", relative_path).await?;
drop(conn);
drop(db);
patch_first_row_serial_type(db_path, page_size, table_root_page, patch)?;
remove_db_sidecars(db_path)?;
Ok(())
}
/// Replace the serial type of one column in the first row of a table page. Both
/// serial types must store the same number of bytes, so that the rest of the
/// record keeps its place.
fn patch_first_row_serial_type(
db_path: &Path,
page_size: usize,
root_page: usize,
patch: FirstRowSerialTypePatch,
) -> Result<()> {
anyhow::ensure!(
sqlite_serial_type_payload_len(u64::from(patch.expected_serial_type))
== sqlite_serial_type_payload_len(u64::from(patch.new_serial_type)),
"serial types {} and {} do not store the same number of bytes",
patch.expected_serial_type,
patch.new_serial_type
);
let mut bytes = std::fs::read(db_path)
.with_context(|| format!("failed to read fixture '{}'", db_path.display()))?;
let page_start = (root_page - 1) * page_size;
anyhow::ensure!(
bytes.len() > page_start + 8,
"fixture too small to patch page {root_page}"
);
let cell_count = u16::from_be_bytes([bytes[page_start + 3], bytes[page_start + 4]]) as usize;
anyhow::ensure!(
cell_count >= 1,View on GitHub (pinned to 8d4a589f8d)