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 appropriately

View on GitHub (pinned to 6c72522679)

Solutions

  1. Report with SQL and EXPLAIN — the index-cursor slot holds an incompatible kind at runtime
  2. Force a different plan (drop/disable the index or use NOT INDEXED) so the paired-cursor path is not used
  3. Rewrite the index-driven lookup as a join to change the access path
  4. 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

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


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/fbfa2f9393df0871. Report an issue: GitHub.