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

  1. Report the panic with the SQL — cursor references must stay in lockstep with allocation; this is an internal bug
  2. 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)
  3. Reduce index usage in the query (drop a secondary index or reorder joins) so the problematic allocation branch changes
  4. 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

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


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