tursodatabase/turso · error
VOpen on non-virtual table cursor
Error message
VOpen on non-virtual table cursor
What it means
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.
Source
Thrown at core/vdbe/execute.rs:1476
}
}
state.pc += 1;
Ok(InsnFunctionStepResult::Step)
}
pub fn op_vopen(
program: &Program,
state: &mut ProgramState,
insn: &Insn,
_pager: &Arc<Pager>,
) -> InsnResult {
load_insn!(VOpen { cursor_id }, insn);
let (_, cursor_type) = program
.cursor_ref
.get(*cursor_id)
.expect("cursor_id should exist in cursor_ref");
let CursorType::VirtualTable(virtual_table) = cursor_type else {
panic!("VOpen on non-virtual table cursor");
};
let cursor = virtual_table.open(program.connection.clone())?;
state
.cursors
.get_mut(*cursor_id)
.unwrap_or_else(|| panic!("cursor id {} out of bounds", *cursor_id))
.replace(Cursor::Virtual(cursor));
state.pc += 1;
Ok(InsnFunctionStepResult::Step)
}
pub fn op_vcreate(
program: &Program,
state: &mut ProgramState,
insn: &Insn,
_pager: &Arc<Pager>,
) -> InsnResult {
load_insn!(View on GitHub (pinned to 492c4a71cd)
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
Defensive patterns
Strategy: type-guard
Validate before calling
// before stepping a program that opens vtab cursors, verify their kinds
for insn in &program.insns {
if let Insn::VOpen { cursor_id } = insn {
let Some((_, CursorType::VirtualTable(_))) = program.cursor_ref.get(*cursor_id) else {
crate::bail_parse_error!("VOpen on non-virtual cursor {cursor_id}");
};
}
} Type guard
fn is_virtual_cursor(program: &Program, id: CursorID) -> bool {
matches!(
program.cursor_ref.get(id).map(|(_, t)| t),
Some(CursorType::VirtualTable(_))
)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Extensions registering vtabs whose names collide with user tables or views; schema changes between prepare and execute; complex DML over vtabs.
Related errors
- OpenRead on virtual table cursor, use Insn:VOpen instead
- VUpdate on non-virtual table cursor
- 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/463e65b6ddec4b60.
Report an issue: GitHub.