tursodatabase/turso · critical

Function should only be called after `SQLITE_ROW`

Error message

Function should only be called after `SQLITE_ROW`

What it means

In the C compatibility layer, sqlite3_column_type calls stmt.row().expect("Function should only be called after SQLITE_ROW"). If no row is current - never stepped, stepped to SQLITE_DONE, or after reset - the expect panics, and a panic across the C FFI boundary aborts the process. This is stricter than upstream sqlite3, which tolerates column access after DONE.

Source

Thrown at bindings/c/src/lib.rs:2327

        return SQLITE_MISUSE;
    }

    let stmt_ref = &mut *stmt;
    stmt_ref.stmt.clear_bindings();

    SQLITE_OK
}

#[no_mangle]
pub unsafe extern "C" fn sqlite3_column_type(
    stmt: *mut sqlite3_stmt,
    idx: ffi::c_int,
) -> ffi::c_int {
    let stmt = &mut *stmt;
    let row = stmt
        .stmt
        .row()
        .expect("Function should only be called after `SQLITE_ROW`");

    match row.get::<&Value>(idx as usize) {
        Ok(turso_core::Value::Numeric(turso_core::Numeric::Integer(_))) => SQLITE_INTEGER,
        Ok(turso_core::Value::Text(_)) => SQLITE_TEXT,
        Ok(turso_core::Value::Numeric(turso_core::Numeric::Float(_))) => SQLITE_FLOAT,
        Ok(turso_core::Value::Blob(_)) => SQLITE_BLOB,
        _ => SQLITE_NULL,
    }
}

#[no_mangle]
pub unsafe extern "C" fn sqlite3_column_count(stmt: *mut sqlite3_stmt) -> ffi::c_int {
    let stmt = &mut *stmt;
    stmt.stmt.num_columns() as ffi::c_int
}

#[no_mangle]
pub unsafe extern "C" fn sqlite3_column_decltype(

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Gate every column access on `sqlite3_step(stmt) == SQLITE_ROW`
  2. Check every sqlite3_step return code (ROW, DONE, BUSY, ERROR) before touching columns
  3. After sqlite3_reset, always step again before any column call
  4. Refactor code that relies on sqlite3's post-DONE leniency - this binding deliberately aborts instead

Example fix

// before
sqlite3_step(stmt);
int t = sqlite3_column_type(stmt, 0); // aborts when step returned SQLITE_DONE
// after
if (sqlite3_step(stmt) == SQLITE_ROW) {
    int t = sqlite3_column_type(stmt, 0);
}
Defensive patterns

Strategy: validation

Validate before calling

int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
    int t = sqlite3_column_type(stmt, 0);
    /* safe */
}

Prevention

When it happens

Trigger: Calling sqlite3_column_type (or sibling column accessors routed the same way) before the first successful sqlite3_step, after a step returned SQLITE_DONE/ERROR/BUSY, or after sqlite3_reset - i.e. without a fresh SQLITE_ROW.

Common situations: Ported C code that forgets to check the step return code, do-while loop structures that call column functions unconditionally, or code relying on sqlite3's lenient post-DONE behavior.

Related errors


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