tursodatabase/turso · error · InvalidOperationException

Column name {name} is ambiguous between {column1} and {colum

Error message

Column name {name} is ambiguous between {column1} and {column2}.

What it means

InvalidOperationException thrown by GetOrdinal when the exact case-sensitive lookup finds nothing but the case-insensitive fallback matches two or more columns whose names differ only by casing (e.g. 'ID' and 'id' from a JOIN). The reader first tries an ordinal match, then collects case-insensitive matches; more than one is ambiguous because the caller's intent cannot be determined. The message names both conflicting columns.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteDataReader.cs:380

    public override int GetOrdinal(string name)
    {
        EnsureOpen();
        ArgumentNullException.ThrowIfNull(name);
        _ = GetStatement();
        for (var i = 0; i < FieldCount; i++)
        {
            if (string.Equals(GetName(i), name, StringComparison.Ordinal))
                return i;
        }

        string? match = null;
        var matchOrdinal = -1;
        for (var i = 0; i < FieldCount; i++)
        {
            if (string.Equals(GetName(i), name, StringComparison.OrdinalIgnoreCase))
            {
                if (match is not null)
                    throw new InvalidOperationException(Properties.Resources.AmbiguousColumnName(name, match, GetName(i)));

                match = GetName(i);
                matchOrdinal = i;
            }
        }

        if (match is not null)
            return matchOrdinal;

        throw new ArgumentOutOfRangeException(nameof(name), name, $"Column {name} was not found.");
    }

    public override DataTable GetSchemaTable()
    {
        EnsureOpen();
        var statement = GetStatement();
        var schema = new DataTable("SchemaTable");
        schema.Columns.Add(SchemaTableColumn.ColumnName, typeof(string));

View on GitHub (pinned to 244cde92a7)

Solutions

  1. Use the exact casing of the intended column — the ordinal (case-sensitive) pass runs first and returns immediately.
  2. Alias the duplicated columns in the query: SELECT a.Id AS a_id, b.id AS b_id, then GetOrdinal("a_id").
  3. As a last resort, resolve by ordinal (SELECT column order) or via reader.GetName(i) scans.

Example fix

// before
SELECT a.Id, b.id FROM orders a JOIN users b ON ...;
var i = reader.GetOrdinal("id"); // ambiguous

// after
SELECT a.Id AS order_id, b.id AS user_id FROM orders a JOIN users b ON ...;
var i = reader.GetOrdinal("user_id");
Defensive patterns

Strategy: validation

Validate before calling

int ResolveExact(SqliteDataReader r, string name)
{
    var hits = Enumerable.Range(0, r.FieldCount)
        .Where(i => string.Equals(r.GetName(i), name, StringComparison.OrdinalIgnoreCase)).ToList();
    if (hits.Count > 1) throw new InvalidOperationException($"'{name}' matches {hits.Count} columns; use exact case or alias.");
    return hits.Count == 1 ? hits[0] : throw new ArgumentOutOfRangeException(nameof(name), name);
}

Try / catch

try { var i = reader.GetOrdinal(name); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ambiguous"))
{ /* switch to exact-case name or a query alias */ }

Prevention

When it happens

Trigger: SELECT a.Id, b.id FROM t a JOIN u b ... then GetOrdinal("id"); queries over quoted mixed-case identifiers created with "MyColumn" vs MYCOLUMN; SQLite's ASCII case-insensitive identifier rules letting two such columns coexist.

Common situations: Joining tables with inconsistent identifier casing conventions; databases originally created on case-sensitive filesystems or ported from other engines; ORM-generated joins exposing both spellings in one projection.

Related errors


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