{"record":{"id":"463e65b6ddec4b60","repo":"tursodatabase/turso","slug":"vopen-on-non-virtual-table-cursor","errorCode":null,"errorMessage":"VOpen on non-virtual table cursor","messagePattern":"VOpen on non-virtual table cursor","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/vdbe/execute.rs","lineNumber":1476,"sourceCode":"        }\n    }\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vopen(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> InsnResult {\n    load_insn!(VOpen { cursor_id }, insn);\n    let (_, cursor_type) = program\n        .cursor_ref\n        .get(*cursor_id)\n        .expect(\"cursor_id should exist in cursor_ref\");\n    let CursorType::VirtualTable(virtual_table) = cursor_type else {\n        panic!(\"VOpen on non-virtual table cursor\");\n    };\n    let cursor = virtual_table.open(program.connection.clone())?;\n    state\n        .cursors\n        .get_mut(*cursor_id)\n        .unwrap_or_else(|| panic!(\"cursor id {} out of bounds\", *cursor_id))\n        .replace(Cursor::Virtual(cursor));\n    state.pc += 1;\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_vcreate(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> InsnResult {\n    load_insn!(","sourceCodeStart":1458,"sourceCodeEnd":1494,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/core/vdbe/execute.rs#L1458-L1494","documentation":"Insn::VOpen executed on a cursor whose cursor_ref type is not CursorType::VirtualTable. VOpen instantiates a module cursor via virtual_table.open(connection), so any other cursor kind means the translator emitted a virtual-table opcode against a regular (or pseudo/sorter) cursor slot — a malformed program, panicked at the start of op_vopen.","triggerScenarios":"Translating statements over virtual tables where the cursor was allocated as a B-tree cursor — for example a name resolving to both a real table and a vtab in different schema contexts, or vtab detection missed in a subplan (trigger, RETURNING, upsert) that shares cursor ids with the vtab path.","commonSituations":"Extensions registering vtabs whose names collide with user tables or views; schema changes between prepare and execute; complex DML over vtabs.","solutions":["Report with schema plus SQL — VOpen must only target cursors allocated as VirtualTable","Check for name collisions between your virtual modules and real tables/views and rename to disambiguate","Simplify the statement (remove triggers/RETURNING clauses) to bypass the shared-cursor path","Upgrade"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// before stepping a program that opens vtab cursors, verify their kinds\nfor insn in &program.insns {\n    if let Insn::VOpen { cursor_id } = insn {\n        let Some((_, CursorType::VirtualTable(_))) = program.cursor_ref.get(*cursor_id) else {\n            crate::bail_parse_error!(\"VOpen on non-virtual cursor {cursor_id}\");\n        };\n    }\n}","typeGuard":"fn is_virtual_cursor(program: &Program, id: CursorID) -> bool {\n    matches!(\n        program.cursor_ref.get(id).map(|(_, t)| t),\n        Some(CursorType::VirtualTable(_))\n    )\n}","tryCatchPattern":null,"preventionTips":["Avoid module names that can shadow real tables; qualify schema names","Audit VOpen/VUpdate/VColumn targets in debug program checks","Wrap execution boundaries with catch_unwind so vtab codegen bugs become query errors"],"tags":["vdbe","executor","virtual-table","cursor-type","panic","runtime"],"backgroundTag":"virtual-table-instruction-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"}