tursodatabase/turso · error

Rewind on non-btree/materialized-view cursor

Error message

Rewind on non-btree/materialized-view cursor

What it means

Insn::Rewind (position a scan cursor at its first row) executed on a cursor that is neither a BTree nor a MaterializedView runtime cursor. Rewind only works on B-tree-backed objects; sorter, pseudo, and virtual table cursors use different opcodes (SorterRewind, VFilter/VNext). The program advanced a cursor of the wrong kind — a codegen bug caught mid-execution in op_rewind.

Source

Thrown at core/vdbe/execute.rs:1786

    );
    assert!(pc_if_empty.is_offset());
    // Clear any bloom filter associated with this cursor so stale filter data
    // does not incorrectly reject valid matches in subsequent iterations.
    if let Some(filter) = state.get_bloom_filter_mut(*cursor_id) {
        filter.clear();
    }
    let is_empty = {
        let cursor = state.get_cursor(*cursor_id);
        match cursor {
            Cursor::BTree(btree_cursor) => {
                return_if_io!(btree_cursor.rewind());
                btree_cursor.is_empty()
            }
            Cursor::MaterializedView(mv_cursor) => {
                return_if_io!(mv_cursor.rewind());
                !mv_cursor.is_valid()?
            }
            _ => panic!("Rewind on non-btree/materialized-view cursor"),
        }
    };
    if is_empty {
        state.pc = pc_if_empty.as_offset_int();
    } else {
        // Rewind positions to the first row, which is effectively a read
        state.record_rows_read(1);
        state.pc += 1;
    }
    Ok(InsnFunctionStepResult::Step)
}

pub fn op_last(
    program: &Program,
    state: &mut ProgramState,
    insn: &Insn,
    _pager: &Arc<Pager>,
) -> InsnResult {

View on GitHub (pinned to 6c72522679)

Solutions

  1. Report the SQL — Rewind must only target BTreeTable/BTreeIndex/MaterializedView cursors
  2. Confirm the shape by removing ORDER BY / DISTINCT / vtab references one at a time to find which cursor kind is mis-targeted
  3. Restructure so the suspicious construct sits in its own query level
  4. Upgrade
Defensive patterns

Strategy: validation

Validate before calling

// Rewind must target btree-backed cursors only
for insn in &program.insns {
    if let Insn::Rewind { 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!("Rewind on non-btree cursor ({other:?})"),
        }
    }
}

Prevention

When it happens

Trigger: Full-scan emission (Rewind) resolved to a cursor that was actually allocated as a sorter (ORDER BY pipeline), pseudo (VALUES/co-routine), or virtual table cursor — typically after cursor id reuse across subquery flattening or changes to which opcode the loop emitter picks.

Common situations: Queries mixing ORDER BY/DISTINCT with flattened subqueries; engine upgrades changing flattening or loop-emission rules.

Related errors


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