tursodatabase/turso · error

RowKey::Record requires Index cursor type

Error message

RowKey::Record requires Index cursor type

What it means

When an MVCC cursor's current key is RowKey::Record (index or WITHOUT ROWID-style record), the rowid is recovered from the last column of the index record - which only exists for index cursors (MvccCursorType::Index with has_rowid). On a table cursor this panics; for rowid-less indexes the code instead bails with 'Indexes without rowid are not supported in MVCC'.

Source

Thrown at core/mvcc/cursor.rs:1523

        Ok(IOResult::Done(()))
    }

    fn rowid(&mut self) -> IOResultOr<Option<i64>> {
        if self.get_null_flag() {
            return Ok(IOResult::Done(None));
        }
        let rowid = match self.get_current_pos() {
            CursorPosition::Loaded {
                row_id,
                in_btree: _,
                ..
            } => match &row_id.row_id {
                RowKey::Int(id) => Some(*id),
                RowKey::Record(sortable_key) => {
                    // For index cursors, the rowid is stored in the last column of the index record
                    let MvccCursorType::Index(index_info) = &self.mv_cursor_type else {
                        panic!("RowKey::Record requires Index cursor type");
                    };
                    if index_info.has_rowid {
                        match sortable_key.key.last_value() {
                            Some(Ok(crate::types::ValueRef::Numeric(
                                crate::numeric::Numeric::Integer(rowid),
                            ))) => Some(rowid),
                            _ => {
                                crate::bail_parse_error!("Failed to parse rowid from index record")
                            }
                        }
                    } else {
                        crate::bail_parse_error!("Indexes without rowid are not supported in MVCC");
                    }
                }
            },
            CursorPosition::BeforeFirst => None,
            CursorPosition::End => None,
        };

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Ensure record-keyed access only goes through cursors opened as MvccCursorType::Index
  2. Avoid WITHOUT ROWID tables and rowid-less indexes under experimental MVCC until supported
  3. If hit from SQL, capture the EXPLAIN plan and report it

Example fix

// before
let rowid = extract_rowid(&table_cursor); // RowKey::Record on a Table cursor -> panic

// after
let rowid = match cursor_type {
    MvccCursorType::Index(_) => extract_rowid_from_index(cursor),
    MvccCursorType::Table => extract_rowid_int(cursor),
};
Defensive patterns

Strategy: type-guard

Validate before calling

if let MvccCursorType::Index(info) = cursor_type {
    if !info.has_rowid { crate::bail_parse_error!("index without rowid"); }
    // only now read the rowid from RowKey::Record
}

Type guard

fn is_index_cursor(t: &MvccCursorType) -> bool {
    matches!(t, MvccCursorType::Index(_))
}

Prevention

When it happens

Trigger: Internal: rowid extraction from get_current_pos invoked on a Table-type cursor whose key ended up as RowKey::Record, or on index cursors whose has_rowid is false (which returns a parse error instead).

Common situations: MVCC workloads mixing WITHOUT ROWID tables or covering-index plans; contributor changes to cursor typing.

Related errors


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