tursodatabase/turso · error
Cursor not found: {cursor_id}
Error message
Cursor not found: {cursor_id} What it means
resolve_index_for_cursor_id() maps a CursorID to the Arc<Index> of the index it scans; the panic fires when cursor_id does not index into the program builder's cursor_ref vector at all. Translation referenced a cursor slot that was never allocated by an OpenRead/cursor-allocation step. This is a bytecode-builder bookkeeping failure that occurs while compiling SQL, before any execution.
Source
Thrown at core/vdbe/builder.rs:1886
.unwrap_or_else(|| panic!("No index cursor found for table {table_ref_id}"))
}
pub fn resolve_any_index_cursor_id_for_table_safe(
&self,
table_ref_id: TableInternalId,
) -> Option<CursorID> {
self.cursor_ref.iter().position(|(k, _)| {
k.as_ref()
.is_some_and(|k| k.table_reference_id == table_ref_id && k.index.is_some())
})
}
/// Resolve the [Index] that a given cursor is associated with.
pub fn resolve_index_for_cursor_id(&self, cursor_id: CursorID) -> Arc<Index> {
let cursor_ref = &self
.cursor_ref
.get(cursor_id)
.unwrap_or_else(|| panic!("Cursor not found: {cursor_id}"))
.1;
let CursorType::BTreeIndex(index) = cursor_ref else {
panic!("Cursor is not an index: {cursor_id}");
};
index.clone()
}
/// Get the [CursorType] of a given cursor.
pub fn get_cursor_type(&self, cursor_id: CursorID) -> Option<&CursorType> {
self.cursor_ref
.get(cursor_id)
.map(|(_, cursor_type)| cursor_type)
}
pub const fn set_collation(&mut self, c: Option<(CollationSeq, bool)>) {
self.collation = c
}
View on GitHub (pinned to 492c4a71cd)
Solutions
- Report the panic with the SQL — cursor references must stay in lockstep with allocation; this is an internal bug
- If you maintain a fork, audit the failing translate/ path for a CursorID captured before conditional cursor allocation (compare the index branch vs table branch of the plan)
- Reduce index usage in the query (drop a secondary index or reorder joins) so the problematic allocation branch changes
- Upgrade to a version with the allocation fix
Example fix
// before: assuming the id was allocated
let index = program_builder.resolve_index_for_cursor_id(cursor_id);
// after: check the slot first and surface a translation error
let Some((_, CursorType::BTreeIndex(index))) = cursor_ref.get(cursor_id) else {
crate::bail_parse_error!("cursor {cursor_id} not allocated as an index cursor");
};
index.clone() Defensive patterns
Strategy: type-guard
Validate before calling
if builder.get_cursor_type(cursor_id).is_none() {
crate::bail_parse_error!("cursor {cursor_id} was never allocated");
} Type guard
fn index_cursor(b: &ProgramBuilder, id: CursorID) -> Option<&Arc<Index>> {
match b.get_cursor_type(id) {
Some(CursorType::BTreeIndex(idx)) => Some(idx),
_ => None,
}
} Prevention
- Never carry a CursorID across a conditional allocation branch without re-resolving it
- Run a post-translation debug pass verifying every cursor reference exists in cursor_ref
- Run cargo test plus the sqltest conformance runner after touching cursor allocation
When it happens
Trigger: Codegen paths that hold a CursorID across conditional allocation branches (index lookup vs table scan) and then resolve the index for an id from the branch that was not taken; emitting index opcodes before their cursor is allocated; cursor renumbering/compaction dropping a slot that is still referenced.
Common situations: Forks or PRs that reorder emit_open_read calls in core/translate/; plans with many tables and secondary indexes where cursor ids shift; version upgrades changing cursor numbering.
Related errors
- No index cursor found for table {table_ref_id}
- Cursor is not an index: {cursor_id}
- cursor id {cursor_id} out of bounds
- cursor id {} out of bounds
- Cursor not found: {key:?}
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/a7d1a1bc0deca499.
Report an issue: GitHub.