{"record":{"id":"77681417479ac661","repo":"tursodatabase/turso","slug":"rewind-on-non-btree-materialized-view-cursor","errorCode":null,"errorMessage":"Rewind on non-btree/materialized-view cursor","messagePattern":"Rewind on non-btree/materialized-view cursor","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/vdbe/execute.rs","lineNumber":1786,"sourceCode":"    );\n    assert!(pc_if_empty.is_offset());\n    // Clear any bloom filter associated with this cursor so stale filter data\n    // does not incorrectly reject valid matches in subsequent iterations.\n    if let Some(filter) = state.get_bloom_filter_mut(*cursor_id) {\n        filter.clear();\n    }\n    let is_empty = {\n        let cursor = state.get_cursor(*cursor_id);\n        match cursor {\n            Cursor::BTree(btree_cursor) => {\n                return_if_io!(btree_cursor.rewind());\n                btree_cursor.is_empty()\n            }\n            Cursor::MaterializedView(mv_cursor) => {\n                return_if_io!(mv_cursor.rewind());\n                !mv_cursor.is_valid()?\n            }\n            _ => panic!(\"Rewind on non-btree/materialized-view cursor\"),\n        }\n    };\n    if is_empty {\n        state.pc = pc_if_empty.as_offset_int();\n    } else {\n        // Rewind positions to the first row, which is effectively a read\n        state.record_rows_read(1);\n        state.pc += 1;\n    }\n    Ok(InsnFunctionStepResult::Step)\n}\n\npub fn op_last(\n    program: &Program,\n    state: &mut ProgramState,\n    insn: &Insn,\n    _pager: &Arc<Pager>,\n) -> InsnResult {","sourceCodeStart":1768,"sourceCodeEnd":1804,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/core/vdbe/execute.rs#L1768-L1804","documentation":"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.","triggerScenarios":"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.","commonSituations":"Queries mixing ORDER BY/DISTINCT with flattened subqueries; engine upgrades changing flattening or loop-emission rules.","solutions":["Report the SQL — Rewind must only target BTreeTable/BTreeIndex/MaterializedView cursors","Confirm the shape by removing ORDER BY / DISTINCT / vtab references one at a time to find which cursor kind is mis-targeted","Restructure so the suspicious construct sits in its own query level","Upgrade"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Rewind must target btree-backed cursors only\nfor insn in &program.insns {\n    if let Insn::Rewind { cursor_id, .. } = insn {\n        match program.cursor_ref.get(*cursor_id).map(|(_, t)| t) {\n            Some(CursorType::BTreeTable(_)) | Some(CursorType::BTreeIndex(_))\n            | Some(CursorType::MaterializedView(..)) => {}\n            other => crate::bail_parse_error!(\"Rewind on non-btree cursor ({other:?})\"),\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Include ORDER BY/DISTINCT/vtab plus subquery mixes in the CI corpus","Debug-build program audits for loop opcodes vs cursor kinds","Boundary catch_unwind to convert engine panics into query failures"],"tags":["vdbe","executor","cursor-type","table-scan","panic","runtime"],"backgroundTag":"bytecode-cursor-type-mismatch","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}