tursodatabase/turso · error

cursor id {} out of bounds

Error message

cursor id {} out of bounds

What it means

Inside op_vopen the cursor id exists in program.cursor_ref (the earlier expect passed) but state.cursors has no slot at that index: the runtime cursor array is smaller than the program's cursor table. The ProgramState was not sized for every cursor the program declares, an allocation mismatch between program building and state initialization.

Source

Thrown at core/vdbe/execute.rs:1482

pub fn op_vopen(
    program: &Program,
    state: &mut ProgramState,
    insn: &Insn,
    _pager: &Arc<Pager>,
) -> InsnResult {
    load_insn!(VOpen { cursor_id }, insn);
    let (_, cursor_type) = program
        .cursor_ref
        .get(*cursor_id)
        .expect("cursor_id should exist in cursor_ref");
    let CursorType::VirtualTable(virtual_table) = cursor_type else {
        panic!("VOpen on non-virtual table cursor");
    };
    let cursor = virtual_table.open(program.connection.clone())?;
    state
        .cursors
        .get_mut(*cursor_id)
        .unwrap_or_else(|| panic!("cursor id {} out of bounds", *cursor_id))
        .replace(Cursor::Virtual(cursor));
    state.pc += 1;
    Ok(InsnFunctionStepResult::Step)
}

pub fn op_vcreate(
    program: &Program,
    state: &mut ProgramState,
    insn: &Insn,
    _pager: &Arc<Pager>,
) -> InsnResult {
    load_insn!(
        VCreate {
            module_name,
            table_name,
            args_reg,
        },
        insn

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Report with the SQL — all cursors must be allocated before execution; likely a missing state.cursors resize for late-added cursors
  2. If embedding, re-prepare statements after schema (DDL) changes instead of reusing cached ones
  3. Move trigger logic into explicit statements to avoid subprogram cursor growth
  4. Upgrade

Example fix

// contributor fix: size ProgramState from the program's cursor table, not a stale count
let mut state = ProgramState::new(program.cursor_ref.len());
Defensive patterns

Strategy: validation

Validate before calling

// state must cover every cursor the program declares
debug_assert!(
    state.cursors.len() >= program.cursor_ref.len(),
    "ProgramState cursor slots must cover program.cursor_ref"
);

Prevention

When it happens

Trigger: Programs whose cursor count grew after ProgramState was created — subprograms (trigger bodies, co-routines) adding cursors, a resize step skipped when the builder emits more cursors than at state-construction time, or statement reuse against a re-prepared program shape.

Common situations: Trigger-heavy schemas; prepared statements cached across schema changes; refactors of cursor accounting in vdbe.

Related errors


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