tursodatabase/turso · error · IndexOutOfRangeException

column {name} not found

Error message

column {name} not found

What it means

TursoDataReader.GetOrdinal(name) scans the current statement's result columns via GetName and throws IndexOutOfRangeException when no column matches the given name. The comparison is ordinal ('==' on strings), so it is case-sensitive and whitespace-sensitive, unlike some ADO.NET providers that fall back to case-insensitive matching. It only sees columns actually projected by the executed statement, not all table columns.

Source

Thrown at bindings/dotnet/src/Turso.Data/TursoDataReader.cs:182

        return TursoBindings.GetValue(_statement, ordinal).IntValue;
    }

    public override string GetName(int ordinal)
    {
        return TursoBindings.GetName(_statement, ordinal);
    }

    public override int GetOrdinal(string name)
    {
        var fields = TursoBindings.GetFieldCount(_statement);
        for (var i = 0; i < fields; i++)
        {
            var columnName = TursoBindings.GetName(_statement, i);
            if (columnName == name)
                return i;
        }

        throw new IndexOutOfRangeException($"column {name} not found");
    }

    public override string GetString(int ordinal)
    {
        return TursoBindings.GetValue(_statement, ordinal).StringValue;
    }

    public override object GetValue(int ordinal)
    {
        var value = TursoBindings.GetValue(_statement, ordinal);
        return value.ValueType switch
        {
            TursoValueType.Null or TursoValueType.Empty => DBNull.Value,
            TursoValueType.Integer => value.IntValue,
            TursoValueType.Real => value.RealValue,
            TursoValueType.Text => value.StringValue,
            TursoValueType.Blob => value.BlobValue,
            _ => throw new ArgumentOutOfRangeException()

View on GitHub (pinned to 6c72522679)

Solutions

  1. Use the exact column name as returned by the query, including casing (check with reader.GetName(i) for i in 0..FieldCount-1).
  2. Prefer explicit column lists in SELECT instead of '*' so names are stable across schema changes.
  3. Do the case-insensitive fallback yourself: enumerate names once and match with StringComparison.OrdinalIgnoreCase.
  4. Confirm the reader is on the statement you think it is (call NextResult() as needed in batch scenarios).

Example fix

// before
var idx = reader.GetOrdinal("UserName"); // column is actually 'username'

// after
var idx = Enumerable.Range(0, reader.FieldCount)
    .First(i => string.Equals(reader.GetName(i), "UserName", StringComparison.OrdinalIgnoreCase));
Defensive patterns

Strategy: validation

Validate before calling

int? FindColumn(DbDataReader reader, string name)
{
    for (var i = 0; i < reader.FieldCount; i++)
        if (string.Equals(reader.GetName(i), name, StringComparison.OrdinalIgnoreCase))
            return i;
    return null;
}

Try / catch

try { idx = reader.GetOrdinal(name); } catch (IndexOutOfRangeException) { /* log available columns, rethrow as schema-drift error */ }

Prevention

When it happens

Trigger: Calling GetOrdinal("UserName") when the query returned "username"; calling GetOrdinal after a SELECT that aliases or omits the column; calling it when the reader is positioned on a statement whose result set has different columns (multi-statement batches); passing a name with stray whitespace.

Common situations: SELECT * queries after a schema migration renamed/dropped a column; column aliases in the SQL differing from what the reader code expects; code shared between database engines with different identifier casing; ordinal lookups after DDL statements that return zero columns.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/1194c0cb910ff83f. Report an issue: GitHub.