{"record":{"id":"be6669604624ef35","repo":"tursodatabase/turso","slug":"cursor-is-not-an-index-cursor-id","errorCode":null,"errorMessage":"Cursor is not an index: {cursor_id}","messagePattern":"Cursor is not an index: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/vdbe/builder.rs","lineNumber":1889,"sourceCode":"    pub fn resolve_any_index_cursor_id_for_table_safe(\n        &self,\n        table_ref_id: TableInternalId,\n    ) -> Option<CursorID> {\n        self.cursor_ref.iter().position(|(k, _)| {\n            k.as_ref()\n                .is_some_and(|k| k.table_reference_id == table_ref_id && k.index.is_some())\n        })\n    }\n\n    /// Resolve the [Index] that a given cursor is associated with.\n    pub fn resolve_index_for_cursor_id(&self, cursor_id: CursorID) -> Arc<Index> {\n        let cursor_ref = &self\n            .cursor_ref\n            .get(cursor_id)\n            .unwrap_or_else(|| panic!(\"Cursor not found: {cursor_id}\"))\n            .1;\n        let CursorType::BTreeIndex(index) = cursor_ref else {\n            panic!(\"Cursor is not an index: {cursor_id}\");\n        };\n        index.clone()\n    }\n\n    /// Get the [CursorType] of a given cursor.\n    pub fn get_cursor_type(&self, cursor_id: CursorID) -> Option<&CursorType> {\n        self.cursor_ref\n            .get(cursor_id)\n            .map(|(_, cursor_type)| cursor_type)\n    }\n\n    pub const fn set_collation(&mut self, c: Option<(CollationSeq, bool)>) {\n        self.collation = c\n    }\n\n    pub const fn curr_collation_ctx(&self) -> Option<(CollationSeq, bool)> {\n        self.collation\n    }","sourceCodeStart":1871,"sourceCodeEnd":1907,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/vdbe/builder.rs#L1871-L1907","documentation":"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.","triggerScenarios":"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.","commonSituations":"Mixed table+index cursor allocation for a single table reference; planner/optimizer changes flipping access paths; development on core/translate/ or the optimizer.","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"],"exampleFix":"// before: assumes the resolved cursor is always an index cursor\nlet idx = program_builder.resolve_index_for_cursor_id(cid);\n\n// after: check the cursor kind and handle the table-cursor case\nmatch program_builder.get_cursor_type(cid) {\n    Some(CursorType::BTreeIndex(idx)) => idx.clone(),\n    Some(CursorType::BTreeTable(_)) => { /* fall back to table-cursor path */ }\n    other => crate::bail_parse_error!(\"cursor {cid} is not table or index ({other:?})\"),\n}","handlingStrategy":"type-guard","validationCode":"if !matches!(builder.get_cursor_type(cid), Some(CursorType::BTreeIndex(_))) {\n    crate::bail_parse_error!(\"cursor {cid} must be an index cursor for this instruction\");\n}","typeGuard":"fn is_index_cursor(b: &ProgramBuilder, id: CursorID) -> bool {\n    matches!(b.get_cursor_type(id), Some(CursorType::BTreeIndex(_)))\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["vdbe","codegen","cursor-type","index","panic","prepare-time"],"backgroundTag":"cursor-type-mismatch","analyzedSha":"492c4a71cd7c2649e7df83da1471b74f4b1c7aa9","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}