tursodatabase/turso · error
Cursor is not an index: {cursor_id}
Error message
Cursor is not an index: {cursor_id} What it means
resolve_index_for_cursor_id() found the cursor slot but its CursorType is not BTreeIndex, so there is no Index to return and it panics. The generated bytecode assumed an index cursor where a table (or pseudo/sorter/virtual) cursor was actually opened. It is a codegen inconsistency between the kind of cursor allocated and the instructions emitted against it.
Source
Thrown at core/vdbe/builder.rs:1889
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
}
pub const fn curr_collation_ctx(&self) -> Option<(CollationSeq, bool)> {
self.collation
}View on GitHub (pinned to 492c4a71cd)
Solutions
- Report with the SQL and EXPLAIN output — the opcode/cursor-kind pair is inconsistent
- Compare bytecode against sqlite3 with scripts/diff.sh or EXPLAIN to see which cursor kind the reference engine uses
- Change the access path (drop/disable the index, or NOT INDEXED) so only table cursors are involved
- Upgrade
Example fix
// before: assumes the resolved cursor is always an index cursor
let idx = program_builder.resolve_index_for_cursor_id(cid);
// after: check the cursor kind and handle the table-cursor case
match program_builder.get_cursor_type(cid) {
Some(CursorType::BTreeIndex(idx)) => idx.clone(),
Some(CursorType::BTreeTable(_)) => { /* fall back to table-cursor path */ }
other => crate::bail_parse_error!("cursor {cid} is not table or index ({other:?})"),
} Defensive patterns
Strategy: type-guard
Validate before calling
if !matches!(builder.get_cursor_type(cid), Some(CursorType::BTreeIndex(_))) {
crate::bail_parse_error!("cursor {cid} must be an index cursor for this instruction");
} Type guard
fn is_index_cursor(b: &ProgramBuilder, id: CursorID) -> bool {
matches!(b.get_cursor_type(id), Some(CursorType::BTreeIndex(_)))
} Prevention
- When a table reference can carry both cursor kinds, resolve via get_cursor_type and match on the kind instead of assuming
- Add .sqltest conformance cases covering both index and full-scan plans for the same query shape
When it happens
Trigger: Emitting index-based opcodes whose cursor was opened as BTreeTable — commonly when one table reference gets both cursor kinds (the correlated-subquery limitation documented on resolve_any_index_cursor_id_for_table) and translation picks the wrong slot, or after a plan flips from index scan to full scan without updating the resolution.
Common situations: Mixed table+index cursor allocation for a single table reference; planner/optimizer changes flipping access paths; development on core/translate/ or the optimizer.
Related errors
- Cursor not found: {cursor_id}
- No index cursor found for table {table_ref_id}
- OpenRead on pseudo cursor
- Rewind on non-btree/materialized-view cursor
- unexpected cursor type
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/be6669604624ef35.
Report an issue: GitHub.