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,
},
insnView on GitHub (pinned to 492c4a71cd)
Solutions
- Report with the SQL — all cursors must be allocated before execution; likely a missing state.cursors resize for late-added cursors
- If embedding, re-prepare statements after schema (DDL) changes instead of reusing cached ones
- Move trigger logic into explicit statements to avoid subprogram cursor growth
- 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
- Re-prepare statements after DDL changes; never reuse cached statements across schema versions
- For contributors: assert cursor accounting (cursor_ref length vs state capacity) in debug builds
- Add trigger/co-routine conformance tests whenever touching cursor allocation
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
- cursor id {cursor_id} out of bounds
- OpenRead on pseudo cursor
- Rewind on non-btree/materialized-view cursor
- unexpected cursor type
- Next on non-btree/materialized-view cursor
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/cffd12b72fda4f87.
Report an issue: GitHub.