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.

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)

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.