tursodatabase/turso · error
Insn:Column on virtual table cursor, use Insn:VColumn instea
Error message
Insn:Column on virtual table cursor, use Insn:VColumn instead
What it means
Insn::Column executed on a CursorType::VirtualTable cursor. Virtual table columns must be read with Insn::VColumn, which invokes the module's column callback; generic Column decodes a B-tree record and cannot work on a module cursor. The translator emitted the generic opcode against a virtual table — a codegen bug for that statement shape.
Source
Thrown at core/vdbe/execute.rs:2314
state.registers[dest].set_null();
}
}
CursorType::Pseudo(_) => {
let content_reg = crate::get_cursor!(state, cursor_id)
.as_pseudo_mut()
.content_reg();
return op_column_fetch_pseudo(state, content_reg, column, dest);
}
CursorType::IndexMethod(..) => {
let cursor = state.cursors[cursor_id]
.as_mut()
.expect("cursor should exist");
let cursor = cursor.as_index_method_mut();
let value = return_if_io!(state, cursor.query_column(column));
state.registers[dest].set_value(value);
}
CursorType::VirtualTable(_) => {
panic!("Insn:Column on virtual table cursor, use Insn:VColumn instead");
}
}
Ok(InsnFunctionStepResult::Step)
}
fn apply_column_default(default: &Option<Value>, reg: &mut Register) -> Result<()> {
let Some(default) = default else {
reg.set_null();
return Ok(());
};
match (default, reg) {
(Value::Text(new_text), Register::Value(Value::Text(existing_text))) => {
existing_text.do_extend(new_text)?;
}
(Value::Blob(new_blob), Register::Value(Value::Blob(existing_blob))) => {
existing_blob.do_extend(new_blob)?;
}
(default, reg) => {View on GitHub (pinned to 492c4a71cd)
Solutions
- Report with the module and SQL — translation must emit VColumn for vtab cursors
- Remove RETURNING or wrap access so the vtab is read through a plain SELECT list
- Query the vtab directly instead of through the layered view/trigger
- Upgrade
Example fix
// contributor fix in translate/ column emission: branch on the cursor kind
if matches!(cursor_type, CursorType::VirtualTable(_)) {
program.emit_insn(Insn::VColumn { cursor_id, column, dest_reg });
} else {
program.emit_insn(Insn::Column { cursor_id, column, dest_reg });
} Defensive patterns
Strategy: validation
Validate before calling
// generic Column must not read virtual table cursors
for insn in &program.insns {
if let Insn::Column { cursor_id, .. } = insn {
if matches!(program.cursor_ref.get(*cursor_id).map(|(_, t)| t), Some(CursorType::VirtualTable(_))) {
crate::bail_parse_error!("Column on virtual table cursor; VColumn required");
}
}
} Prevention
- Add RETURNING/views/triggers over vtabs to the extension test matrix
- Debug-build audit pairing Column vs VColumn with cursor kinds
- Validate vtab statements against sqlite3 first
When it happens
Trigger: SELECT lists, RETURNING clauses, or trigger/view bodies over virtual tables in code paths that build column reads generically — e.g. RETURNING on vtab DML, or views/triggers layered over vtab modules like csv, fts, or pragma-backed tables.
Common situations: Extensions and catalog-style vtabs combined with RETURNING, views, or triggers; engine versions where column emission was unified.
Related errors
- OpenRead on virtual table cursor, use Insn:VOpen instead
- VOpen on non-virtual table cursor
- VUpdate on non-virtual table cursor
- unexpected cursor type in op_column_range_fetch body ({other
- OpenRead on pseudo cursor
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/c6b10bdcb193a00e.
Report an issue: GitHub.