tursodatabase/turso · error

invalid position to read current mvcc row

Error message

invalid position to read current mvcc row

What it means

MvccCursor::read_mvcc_current_row is only valid while the cursor sits at CursorPosition::Loaded with in_btree == false - positioned by the MVCC scan path where the row's version chain is already resolved. Calling it at Start/End, at an in-btree position, or before advancing panics; it is an internal API-contract check in core/mvcc.

Source

Thrown at core/mvcc/cursor.rs:726

                })?;
                Ok(IOResult::Done(Some(record_ref)))
            }
            CursorPosition::BeforeFirst => {
                // Before first is not a valid position, so we return none.
                Ok(IOResult::Done(None))
            }
            CursorPosition::End => Ok(IOResult::Done(None)),
        }
    }

    pub fn read_mvcc_current_row(&self) -> Result<Option<Row>> {
        let (row_id, versions) = match &self.current_pos {
            CursorPosition::Loaded {
                row_id,
                in_btree,
                versions,
            } if !in_btree => (row_id, versions),
            _ => panic!("invalid position to read current mvcc row"),
        };
        // Scan path: the range iterator already resolved this row's version
        // chain, so read it directly instead of a second skiplist lookup.
        if let Some(versions) = versions {
            return self.db.read_visible_from_versions(self.tx_id, versions);
        }
        let maybe_index_id = match &self.mv_cursor_type {
            MvccCursorType::Index(_) => Some(self.table_id),
            MvccCursorType::Table => None,
        };
        self.db
            .read_from_table_or_index(self.tx_id, row_id, maybe_index_id)
    }

    pub fn close(self) -> Result<()> {
        Ok(())
    }

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Advance the cursor (next) until it reports a row before reading
  2. Branch on the current position and only read when Loaded with in_btree == false
  3. If reached without touching core code, report with the query and MVCC setup

Example fix

// before
let row = cursor.read_mvcc_current_row()?; // called before any next()

// after
while let IOResult::Done(Some(_)) = return_if_io!(cursor.next()) {
    if let Some(row) = cursor.read_mvcc_current_row()? { /* consume row */ }
}
Defensive patterns

Strategy: validation

Validate before calling

// only read the current row while positioned by the scan path
match cursor.get_current_pos() {
    CursorPosition::Loaded { in_btree: false, .. } => cursor.read_mvcc_current_row(),
    _ => Ok(None),
}

Prevention

When it happens

Trigger: Executor code calling read_mvcc_current_row before the first next() step, after exhaustion, or while the cursor is dual-positioned in the btree half of the MVCC cursor.

Common situations: Contributor changes to MVCC cursor stepping; new scan operators reusing the cursor API incorrectly.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/1fd9c2dca513aac6. Report an issue: GitHub.