tursodatabase/turso · error

OpenRead on pseudo cursor

Error message

OpenRead on pseudo cursor

What it means

At execution time Insn::OpenRead tried to open a cursor slot whose cursor_ref type is CursorType::Pseudo. Pseudo cursors hold one materialized in-memory row (used for VALUES rows and co-routine output) and have no B-tree to open, so OpenRead cannot apply. The bytecode reused a pseudo cursor id as if it were a B-tree cursor — a translation bug, hit inside op_open_read while stepping the program.

Source

Thrown at core/vdbe/execute.rs:1379

                pager,
                maybe_transform_root_page_to_positive(mv_store.as_ref(), *root_page),
                index.as_ref(),
                num_columns,
            )?);
            let index_info = Arc::new(if let Some(mv_store) = mv_store.as_ref() {
                IndexInfo::new_from_index_in(index, mv_store.allocator())?
            } else {
                IndexInfo::new_from_index(index)?
            });
            let cursor =
                maybe_promote_to_mvcc_cursor(btree_cursor, MvccCursorType::Index(index_info))?;
            cursors
                .get_mut(*cursor_id)
                .expect("cursor_id should be valid")
                .replace(Cursor::new_btree(cursor));
        }
        CursorType::Pseudo(_) => {
            panic!("OpenRead on pseudo cursor");
        }
        CursorType::Sorter => {
            panic!("OpenRead on sorter cursor");
        }
        CursorType::IndexMethod(..) => {
            unreachable!("IndexMethod handled above")
        }
        CursorType::VirtualTable(_) => {
            panic!("OpenRead on virtual table cursor, use Insn:VOpen instead");
        }
    }
    state.pc += 1;
    Ok(InsnFunctionStepResult::Step)
}

pub fn op_vopen(
    program: &Program,
    state: &mut ProgramState,

View on GitHub (pinned to 6c72522679)

Solutions

  1. Report the SQL — OpenRead may only target BTreeTable/BTreeIndex/MaterializedView cursors; the translator allocated the wrong kind
  2. Rewrite the VALUES/co-routine construct into a real temp table or plain SELECT to avoid pseudo cursors
  3. Upgrade to a version with the cursor-allocation fix
Defensive patterns

Strategy: validation

Validate before calling

// audit a compiled program before first execution: OpenRead must target btree-backed cursors
fn audit_open_reads(program: &Program) -> Result<()> {
    for insn in &program.insns {
        if let Insn::OpenRead { cursor_id, .. } = insn {
            match program.cursor_ref.get(*cursor_id).map(|(_, t)| t) {
                Some(CursorType::BTreeTable(_)) | Some(CursorType::BTreeIndex(_))
                | Some(CursorType::MaterializedView(..)) => {}
                other => crate::bail_parse_error!("OpenRead targets non-btree cursor ({other:?})"),
            }
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: A plan that materializes rows into a pseudo cursor and later emits OpenRead for the same cursor id — typically after cursor renumbering/reallocation changes, or when a co-routine or VALUES-clause pseudo cursor id leaks into an outer open-table sequence.

Common situations: Queries combining VALUES clauses, co-routine subqueries, or CTE materialization with ordinary table scans; engine versions where cursor allocation was refactored.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/74795af3d86c562d. Report an issue: GitHub.