transact-rs/sqlx · warning

invalid column index

Error message

invalid column index: {}

What it means

In the SQLite driver, the check_col_idx! macro (sqlx-sqlite/src/statement/handle.rs) panics when a column index passed to a StatementHandle accessor cannot be converted to a C `c_int` — i.e. the index is negative or exceeds the c_int range. The sqlite3 C API takes an `int` column index, so any out-of-range Rust usize/i64 index is rejected before reaching SQLite. Reaching this means an internal caller used an invalid column ordinal.

Solutions

  1. Inspect the index value in the panic message and find the query/row access that produced it — usually a column accessed with an explicit numeric index out of bounds.
  2. Prefer name-based access: `row.try_get::<_, T>("column_name")` instead of positional indices.
  3. Check that the row/query being read matches the SQL executed (schema drift between prepared statement and access code).
  4. If it appears inside sqlx itself with a sane index, file an issue with a reproducer — it indicates a driver bug (index not validated before c_int conversion).

Example fix

// before
let name: String = row.try_get(7usize)?; // index out of range

// after
let name: String = row.try_get("name")?;
Defensive patterns

Strategy: validation

Validate before calling

// Before positional access, bound-check the index against the row:
if idx >= row.columns().len() {
    return Err(format!("column index {idx} out of range").into());
}
let val = row.try_get_unchecked::<_, String>(idx)?;

Try / catch

// Use try_get (returns Result) rather than get, and catch/match the ColumnDecode/IndexOutOfBounds error:
match row.try_get::<_, String>(idx) {
    Ok(v) => v,
    Err(sqlx::Error::ColumnNotFound(n)) => fallback_for(n),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Any SQLite code path (column name/type/value lookups in the driver) indexing a statement with a negative or huge index; realistically surfaced by driver bugs or by a `try_get`/`try_get_raw` call whose computed column index overflowed (e.g. from a corrupted ColumnIndex or row with an unexpectedly large index).

Common situations: Users typically see this indirectly: a get("col_name") resolving to an out-of-range ordinal after the query changed, or driver-level issues on very wide result sets; it is essentially an assertion that an internal invariant broke.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/a2131ec95a1921c8. Report an issue: GitHub.

Appendix: source

Thrown at sqlx-sqlite/src/statement/handle.rs:51

unsafe impl Send for StatementHandle {}

// Most of the getters below allocate internally, and unsynchronized access is undefined.
// unsafe impl !Sync for StatementHandle {}

macro_rules! expect_ret_valid {
    ($fn_name:ident($($args:tt)*)) => {{
        let val = $fn_name($($args)*);

        TryFrom::try_from(val)
            // This likely means UB in SQLite itself or our usage of it;
            // signed integer overflow is UB in the C standard.
            .unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name)))
    }}
}

macro_rules! check_col_idx {
    ($idx:ident) => {
        c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx))
    };
}

// might use some of this later
#[allow(dead_code)]
impl StatementHandle {
    pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self {
        Self(ptr)
    }

    #[inline]
    pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 {
        // O(c) access to the connection handle for this statement handle
        // https://sqlite.org/c3ref/db_handle.html
        sqlite3_db_handle(self.0.as_ptr())
    }

    pub(crate) fn read_only(&self) -> bool {

View on GitHub (pinned to 03af8bcc57)