clockworklabs/SpacetimeDB · critical · InvalidOperationException

Unique index point scan returned >1 rows

Error message

Unique index point scan returned >1 rows

What it means

UniqueIndex<Handle,Row,T,RW>.FindSingle serializes the key, does a point scan (FFI.datastore_index_scan_point_bsatn), and enforces the unique-index invariant in user code: after the first matching row, a second MoveNext() must fail. Two rows sharing one key under a declared-unique index is a data-integrity violation — the datastore no longer satisfies the module's index definition, so every FindSingle-based accessor (FindByUnique-style lookups and updates routed through DoUpdate) throws.

Source

Thrown at crates/bindings-csharp/Runtime/Internal/IIndex.cs:141

    }

    protected Row? FindSingle(T key)
    {
        using var s = new MemoryStream();
        using var w = new BinaryWriter(s);
        new RW().Write(w, key);
        var point = s.ToArray();

        using var e = new RawPointIter(indexId, point).GetEnumerator();
        if (!e.MoveNext())
        {
            return null;
        }

        var row = e.Current;
        if (e.MoveNext())
        {
            throw new InvalidOperationException("Unique index point scan returned >1 rows");
        }

        return row;
    }

    protected Row DoUpdate(Row row)
    {
        // Insert the row.
        var bytes = IStructuralReadWrite.ToBytes(row);
        var bytes_len = bytes.Length;
        FFI.datastore_update_bsatn(ITableView<Handle, Row>.tableId, indexId, bytes, ref bytes_len);

        return ITableView<Handle, Row>.IntegrateGeneratedColumns(row, bytes, bytes_len);
    }
}

public abstract class RefUniqueIndex<Handle, Row, T, RW>(string name) : IndexBase<Row>(name)
    where Handle : ITableView<Handle, Row>

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Dedupe the table: scan the index's Filter(key) for affected keys and delete/merge extra rows
  2. If the index was recently made unique, clear or migrate the data before republishing the module
  3. Audit code paths that insert or update rows to delete the old row before inserting a new one with the same unique key
  4. If data is clean and it still throws, capture a database snapshot and report a SpacetimeDB datastore bug

Example fix

// before - index on Email was made [Unique] but old rows still contain duplicates
var user = MyTable.UniqueEmail.FindSingle(email); // throws: 2 rows match

// after - dedupe first, then lookups hold the invariant
foreach (var group in MyTable.Iter().GroupBy(r => r.Email).Where(g => g.Count() > 1))
    foreach (var extra in group.Skip(1)) MyTable.Delete(extra.Id);
var user = MyTable.UniqueEmail.FindSingle(email);
Defensive patterns

Strategy: validation

Validate before calling

// Before inserting/updating through a unique index, check the key's uniqueness yourself:
var existing = MyTable.Iter().Where(r => r.UniqueEmail == email).Take(2).ToList();
if (existing.Count > 1) { /* duplicates already present: dedupe before any unique-index op */ }
if (existing.Count == 1 && existing[0].Id != row.Id) { /* key already taken: reject or update in place */ }

Try / catch

try { var row = uniqueIndexAccessor(key); }
catch (InvalidOperationException ioe) when (ioe.Message == "Unique index point scan returned >1 rows")
{
    // datastore integrity is broken: run a dedupe migration, then retry
}

Prevention

When it happens

Trigger: Calling the generated unique-index accessor (FindSingle) for a key that matches 2+ rows: legacy data already containing duplicates when a unique index is added or made unique; writes that bypassed index maintenance (raw datastore_update_bsatn paths, FFI writes); restoring a database dump into a module whose schema now declares the index unique.

Common situations: Adding [Unique] / changing an index from non-unique to unique on a live table without deduplicating first; module updates that republish schema over existing data; concurrent test harnesses writing directly to the datastore; corrupted database after a crash mid-transaction.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/47b5c2f6a7dd232b. Report an issue: GitHub.