tursodatabase/turso · error
unexpected cursor type
Error message
unexpected cursor type
What it means
Inside op_column's resumable state machine, the Rowid state reads the current rowid from the index cursor before seeking the paired table cursor. The panic fires when that cursor is neither Cursor::BTree nor Cursor::IndexMethod at runtime — the bytecode arranged a rowid fetch through a cursor kind that cannot supply one (sorter, pseudo, virtual table). An internal program inconsistency hit mid-execution.
Source
Thrown at core/vdbe/execute.rs:1986
if let Some(deferred) = state.deferred_seeks[cursor_id].take() {
*state.active_op_state.column() = OpColumnState::Rowid {
index_cursor_id: deferred.index_cursor_id,
table_cursor_id: deferred.table_cursor_id,
};
} else {
*state.active_op_state.column() = OpColumnState::GetColumn;
}
}
OpColumnState::Rowid {
index_cursor_id,
table_cursor_id,
} => {
let Some(rowid) = ({
let index_cursor = state.get_cursor(index_cursor_id);
match index_cursor {
Cursor::BTree(cursor) => return_if_io!(cursor.rowid()),
Cursor::IndexMethod(cursor) => return_if_io!(cursor.query_rowid()),
_ => panic!("unexpected cursor type"),
}
}) else {
fetch.write_null_regs(state);
break 'outer;
};
*state.active_op_state.column() = OpColumnState::Seek {
rowid,
table_cursor_id,
};
}
OpColumnState::Seek {
rowid,
table_cursor_id,
} => {
{
let table_cursor = state.get_cursor(table_cursor_id);
// MaterializedView cursors shouldn't go through deferred seek logic
// but if we somehow get here, handle it appropriatelyView on GitHub (pinned to 6c72522679)
Solutions
- Report with SQL and EXPLAIN — the index-cursor slot holds an incompatible kind at runtime
- Force a different plan (drop/disable the index or use NOT INDEXED) so the paired-cursor path is not used
- Rewrite the index-driven lookup as a join to change the access path
- Upgrade
Defensive patterns
Strategy: type-guard
Validate before calling
// before entering the paired index/table column path, verify the index slot kind
if !matches!(
program.cursor_ref.get(index_cursor_id).map(|(_, t)| t),
Some(CursorType::BTreeIndex(_)) | Some(CursorType::IndexMethod(..))
) {
crate::bail_parse_error!("index cursor {index_cursor_id} cannot supply a rowid");
} Type guard
fn rowid_capable(t: &CursorType) -> bool {
matches!(t, CursorType::BTreeIndex(_) | CursorType::IndexMethod(..))
} Prevention
- Cover covering-index vs table-lookup plan switches in conformance tests
- If using custom index methods, test them through the paired-cursor column path
- Report reproducible cases with EXPLAIN output
When it happens
Trigger: Column-extraction plans that pair an index cursor with a table cursor (index scan then row lookup) where the index slot actually holds a sorter/pseudo/vtab cursor — typically after cursor renumbering, or when index-method cursors are mis-tagged in the plan.
Common situations: Plan flips between covering-index and table-lookup access paths; custom index-method features; engine upgrades changing index cursor handling.
Related errors
- OpenRead on pseudo cursor
- Rewind on non-btree/materialized-view cursor
- Next on non-btree/materialized-view cursor
- {} on unexpected cursor
- OpenRead on sorter cursor
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/fbfa2f9393df0871.
Report an issue: GitHub.