| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- package storage
- import (
- "fmt"
- "strings"
- "sync"
- "testing"
- )
- func uniqueTable(t *testing.T, cols ...Column) (*SchemaManager, *TableManager) {
- t.Helper()
- _, _, schemas, tables := newTestSession(t)
- createTestTable(t, schemas, "t", cols)
- return schemas, tables
- }
- func TestInsertWithRowIDReturnsActualRowID(t *testing.T) {
- _, _, schemas, tables := newTestSession(t)
- createTestTable(t, schemas, "t", []Column{{Name: "id", Type: "INTEGER", PrimaryKey: true}})
- rid, err := tables.InsertWithRowID("t", Row{"id": int64(41)})
- if err != nil {
- t.Fatal(err)
- }
- if rid != 41 {
- t.Fatalf("explicit rowid = %d, want 41", rid)
- }
- // Auto-generated rowid is max+1, never a naive counter or MAX of a scan.
- rid, err = tables.InsertWithRowID("t", Row{})
- if err != nil {
- t.Fatal(err)
- }
- if rid != 42 {
- t.Fatalf("generated rowid = %d, want 42", rid)
- }
- }
- func TestInsertWithRowIDSkipsGaps(t *testing.T) {
- _, _, schemas, tables := newTestSession(t)
- createTestTable(t, schemas, "t", []Column{{Name: "id", Type: "INTEGER", PrimaryKey: true}})
- for _, id := range []int64{100, 5, 7} {
- if _, err := tables.InsertWithRowID("t", Row{"id": id}); err != nil {
- t.Fatal(err)
- }
- }
- rid, err := tables.InsertWithRowID("t", Row{})
- if err != nil {
- t.Fatal(err)
- }
- if rid != 101 {
- t.Fatalf("generated rowid = %d, want 101 (max+1, not a re-used gap)", rid)
- }
- }
- func TestUniqueIndexEnforcesOnInsert(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- err := tables.Insert("t", Row{"id": int64(2), "email": "a@x"})
- if err == nil {
- t.Fatal("expected unique violation on duplicate email")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- // A distinct value still works.
- if err := tables.Insert("t", Row{"id": int64(2), "email": "b@x"}); err != nil {
- t.Fatalf("distinct email should succeed: %v", err)
- }
- }
- func TestUniqueIndexAllowsMultipleNulls(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT", Nullable: true},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- for i := int64(1); i <= 3; i++ {
- if err := tables.Insert("t", Row{"id": i, "email": nil}); err != nil {
- t.Fatalf("NULL insert %d should succeed: %v", i, err)
- }
- }
- if err := tables.Insert("t", Row{"id": int64(4), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(5), "email": "a@x"}); err == nil {
- t.Fatal("expected unique violation for non-NULL duplicate")
- }
- }
- func TestUniqueCompositeIndex(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "a", Type: "TEXT"},
- Column{Name: "b", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_ab", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "a"}, {Name: "b"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "a": "x", "b": "y"}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(2), "a": "x", "b": "z"}); err != nil {
- t.Fatalf("distinct composite should succeed: %v", err)
- }
- if err := tables.Insert("t", Row{"id": int64(3), "a": "x", "b": "y"}); err == nil {
- t.Fatal("expected unique violation for duplicate composite (x,y)")
- }
- }
- func TestCreateUniqueIndexRejectsExistingDuplicates(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- for i := int64(1); i <= 2; i++ {
- if err := tables.Insert("t", Row{"id": i, "email": "dup@x"}); err != nil {
- t.Fatal(err)
- }
- }
- err := tables.CreateUniqueIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}})
- if err == nil {
- t.Fatal("expected CreateUniqueIndex to reject existing duplicates")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- // The index must not be registered after the failed create.
- if schemas.IndexExists("uq_email") {
- t.Fatal("index should not exist after validation failure")
- }
- }
- func TestUniqueIndexEnforcesOnUpdate(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(2), "email": "b@x"}); err != nil {
- t.Fatal(err)
- }
- _, _, err := tables.UpdateByPK("t", "2", func(Row) (Row, error) { return Row{"email": "a@x"}, nil })
- if err == nil {
- t.Fatal("expected unique violation when updating email to existing value")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- }
- func TestUniqueIndexFreesOnDelete(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- if _, deleted, err := tables.DeleteByPK("t", "1"); err != nil || !deleted {
- t.Fatalf("delete: deleted=%v err=%v", deleted, err)
- }
- if err := tables.Insert("t", Row{"id": int64(2), "email": "a@x"}); err != nil {
- t.Fatalf("re-insert after delete should succeed: %v", err)
- }
- }
- func TestUniqueIndexTransactionCommitRejectsDuplicate(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- s := NewSession(schemas, tables)
- if err := s.Begin(); err != nil {
- t.Fatal(err)
- }
- if err := s.Insert("t", Row{"id": int64(2), "email": "a@x"}); err != nil {
- t.Fatalf("staged duplicate insert should not fail until commit: %v", err)
- }
- if err := s.Commit(); err == nil {
- t.Fatal("expected commit to reject unique violation")
- } else if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected commit error: %v", err)
- }
- if _, err := tables.GetByPK("t", "2"); err != ErrKeyNotFound {
- t.Fatalf("conflicting row should not be durable: %v", err)
- }
- }
- func TestUniqueIndexTransactionSwap(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(2), "email": "b@x"}); err != nil {
- t.Fatal(err)
- }
- s := NewSession(schemas, tables)
- if err := s.Begin(); err != nil {
- t.Fatal(err)
- }
- if _, _, err := s.UpdateByPK("t", "1", func(Row) (Row, error) { return Row{"email": "b@x"}, nil }); err != nil {
- t.Fatal(err)
- }
- if _, _, err := s.UpdateByPK("t", "2", func(Row) (Row, error) { return Row{"email": "a@x"}, nil }); err != nil {
- t.Fatal(err)
- }
- if err := s.Commit(); err != nil {
- t.Fatalf("swap of unique values should commit: %v", err)
- }
- }
- func TestUniqueIndexConcurrentInserts(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- const n = 32
- errs := make([]error, n)
- var wg sync.WaitGroup
- for i := 0; i < n; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- errs[i] = tables.Insert("t", Row{"id": int64(i + 1), "email": "same@x"})
- }(i)
- }
- wg.Wait()
- ok := 0
- for _, e := range errs {
- if e == nil {
- ok++
- } else if !strings.Contains(e.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected insert error: %v", e)
- }
- }
- if ok != 1 {
- t.Fatalf("expected exactly one successful insert, got %d", ok)
- }
- if got := kvCount(t, tables, "t"); got != 1 {
- t.Fatalf("expected 1 row, got %d", got)
- }
- }
- func TestUniqueIndexConcurrentTransactions(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- s1 := NewSession(schemas, tables)
- s2 := NewSession(schemas, tables)
- if err := s1.Begin(); err != nil {
- t.Fatal(err)
- }
- if err := s2.Begin(); err != nil {
- t.Fatal(err)
- }
- if err := s1.Insert("t", Row{"id": int64(1), "email": "x@y"}); err != nil {
- t.Fatal(err)
- }
- if err := s2.Insert("t", Row{"id": int64(2), "email": "x@y"}); err != nil {
- t.Fatal(err)
- }
- errs := make([]error, 2)
- var wg sync.WaitGroup
- wg.Add(2)
- go func() { defer wg.Done(); errs[0] = s1.Commit() }()
- go func() { defer wg.Done(); errs[1] = s2.Commit() }()
- wg.Wait()
- ok, conflict := 0, 0
- for _, e := range errs {
- if e == nil {
- ok++
- } else if strings.Contains(e.Error(), "UNIQUE constraint failed") {
- conflict++
- } else {
- t.Fatalf("unexpected commit error: %v", e)
- }
- }
- if ok != 1 || conflict != 1 {
- t.Fatalf("expected one commit and one unique conflict, got ok=%d conflict=%d", ok, conflict)
- }
- if got := kvCount(t, tables, "t"); got != 1 {
- t.Fatalf("expected 1 durable row, got %d", got)
- }
- }
- func TestUniqueIndexUpdateNoChangeDoesNotSelfConflict(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- Column{Name: "name", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "email": "a@x", "name": "old"}); err != nil {
- t.Fatal(err)
- }
- // Update a non-indexed column; the unchanged unique value must not conflict.
- if _, updated, err := tables.UpdateByPK("t", "1", func(Row) (Row, error) { return Row{"name": "new"}, nil }); err != nil || !updated {
- t.Fatalf("no-op unique update: updated=%v err=%v", updated, err)
- }
- }
- func TestUniqueIndexValueEncodingDistinguishesTypes(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "v", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_v", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "v"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "v": int64(1)}); err != nil {
- t.Fatal(err)
- }
- // INTEGER 1 and TEXT "1" are distinct under a unique index.
- if err := tables.Insert("t", Row{"id": int64(2), "v": "1"}); err != nil {
- t.Fatalf("TEXT '1' should be distinct from INTEGER 1: %v", err)
- }
- if err := tables.Insert("t", Row{"id": int64(3), "v": int64(1)}); err == nil {
- t.Fatal("expected duplicate INTEGER 1 to be rejected")
- }
- }
- // TestConcurrentCreateUniqueIndexAndInsert races a duplicate insert against a
- // CREATE UNIQUE INDEX. The invariant is that the unique index can never end up
- // present while two rows share a value: either the index creation wins and the
- // insert fails the uniqueness scan, or the insert wins and the index creation
- // fails validating the existing duplicate.
- func TestConcurrentCreateUniqueIndexAndInsert(t *testing.T) {
- for iter := 0; iter < 200; iter++ {
- _, _, schemas, tables := newTestSession(t)
- createTestTable(t, schemas, "t", []Column{
- {Name: "id", Type: "INTEGER", PrimaryKey: true},
- {Name: "v", Type: "TEXT"},
- })
- if err := tables.Insert("t", Row{"id": int64(1), "v": "x"}); err != nil {
- t.Fatal(err)
- }
- var insertErr, indexErr error
- start := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(2)
- go func() {
- defer wg.Done()
- <-start
- insertErr = tables.Insert("t", Row{"id": int64(2), "v": "x"})
- }()
- go func() {
- defer wg.Done()
- <-start
- indexErr = tables.CreateUniqueIndex(&Index{Name: "uq_v", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "v"}}})
- }()
- close(start)
- wg.Wait()
- if schemas.IndexExists("uq_v") {
- rows, err := tables.Select("t", func(r Row) bool { return fmt.Sprintf("%v", r["v"]) == "x" })
- if err != nil {
- t.Fatal(err)
- }
- if len(rows) != 1 {
- t.Fatalf("iteration %d: unique index present but %d rows with v='x' (insertErr=%v indexErr=%v)", iter, len(rows), insertErr, indexErr)
- }
- }
- }
- }
- // TestConcurrentCreateUniqueIndexAndTransactionCommit exercises the same race
- // against a buffered transaction whose commit validates the final overlay.
- func TestConcurrentCreateUniqueIndexAndTransactionCommit(t *testing.T) {
- for iter := 0; iter < 100; iter++ {
- _, _, schemas, tables := newTestSession(t)
- createTestTable(t, schemas, "t", []Column{
- {Name: "id", Type: "INTEGER", PrimaryKey: true},
- {Name: "v", Type: "TEXT"},
- })
- if err := tables.Insert("t", Row{"id": int64(1), "v": "x"}); err != nil {
- t.Fatal(err)
- }
- s := NewSession(schemas, tables)
- if err := s.Begin(); err != nil {
- t.Fatal(err)
- }
- if err := s.Insert("t", Row{"id": int64(2), "v": "x"}); err != nil {
- t.Fatal(err)
- }
- var commitErr, indexErr error
- start := make(chan struct{})
- var wg sync.WaitGroup
- wg.Add(2)
- go func() {
- defer wg.Done()
- <-start
- commitErr = s.Commit()
- }()
- go func() {
- defer wg.Done()
- <-start
- indexErr = tables.CreateUniqueIndex(&Index{Name: "uq_v", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "v"}}})
- }()
- close(start)
- wg.Wait()
- if schemas.IndexExists("uq_v") {
- rows, err := tables.Select("t", func(r Row) bool { return fmt.Sprintf("%v", r["v"]) == "x" })
- if err != nil {
- t.Fatal(err)
- }
- if len(rows) != 1 {
- t.Fatalf("iteration %d: unique index present but %d rows with v='x' (commitErr=%v indexErr=%v)", iter, len(rows), commitErr, indexErr)
- }
- }
- }
- }
- func TestUniqueIndexIntegralNumericCanonicalization(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "v", Type: "REAL"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_v", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "v"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "v": int64(1)}); err != nil {
- t.Fatal(err)
- }
- // A computed integral real (1.5-0.5) must collide with the integer 1.
- err := tables.Insert("t", Row{"id": int64(2), "v": 1.5 - 0.5})
- if err == nil {
- t.Fatal("expected float64(1.0) to collide with int64(1)")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- // A non-integral real remains distinct.
- if err := tables.Insert("t", Row{"id": int64(3), "v": 1.5}); err != nil {
- t.Fatalf("distinct non-integral real should succeed: %v", err)
- }
- }
- func TestUniqueIndexUnsignedCanonicalization(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "v", Type: "INTEGER"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_v", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "v"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": int64(1), "v": uint64(7)}); err != nil {
- t.Fatal(err)
- }
- // unsigned 7 and signed 7 are the same integer value.
- if err := tables.Insert("t", Row{"id": int64(2), "v": int64(7)}); err == nil {
- t.Fatal("expected uint64(7) to collide with int64(7)")
- }
- }
- func TestUniqueIndexDeleteReinsertTextPK(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "TEXT", PrimaryKey: true},
- Column{Name: "email", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "uq_email", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "email"}}}); err != nil {
- t.Fatal(err)
- }
- if err := tables.Insert("t", Row{"id": "alice", "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- // Deleting and re-inserting the same TEXT primary key with the same unique
- // value must not self-conflict: the re-insert is a new rowid but replaces the
- // same durable data key.
- s := NewSession(schemas, tables)
- if err := s.Begin(); err != nil {
- t.Fatal(err)
- }
- if _, deleted, err := s.DeleteByPK("t", "alice"); err != nil || !deleted {
- t.Fatalf("delete: deleted=%v err=%v", deleted, err)
- }
- if err := s.Insert("t", Row{"id": "alice", "email": "a@x"}); err != nil {
- t.Fatal(err)
- }
- if err := s.Commit(); err != nil {
- t.Fatalf("delete+reinsert same PK should commit, got: %v", err)
- }
- rows, err := tables.Select("t", nil)
- if err != nil || len(rows) != 1 || rows[0]["email"] != "a@x" {
- t.Fatalf("expected exactly one row with email a@x, got %v (err=%v)", rows, err)
- }
- }
- func TestUniqueIndexesSameValueDifferentIndexes(t *testing.T) {
- schemas, tables := uniqueTable(t,
- Column{Name: "id", Type: "INTEGER", PrimaryKey: true},
- Column{Name: "name", Type: "TEXT"},
- Column{Name: "lower_name", Type: "TEXT"},
- )
- if err := schemas.CreateIndex(&Index{Name: "UQE_user_name", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "name"}}}); err != nil {
- t.Fatal(err)
- }
- if err := schemas.CreateIndex(&Index{Name: "UQE_user_lower_name", Table: "t", Unique: true, Columns: []IndexColumn{{Name: "lower_name"}}}); err != nil {
- t.Fatal(err)
- }
- // A single row carrying the same value in two different unique indexes must
- // not self-collide (the seen set is per-index, not per-value).
- if err := tables.Insert("t", Row{"id": int64(1), "name": "alice", "lower_name": "alice"}); err != nil {
- t.Fatalf("same value across two unique indexes should not self-collide: %v", err)
- }
- // A second row with the same value in ONE index must still collide.
- if err := tables.Insert("t", Row{"id": int64(2), "name": "bob", "lower_name": "alice"}); err == nil {
- t.Fatal("expected unique violation on lower_name")
- }
- if err := tables.Insert("t", Row{"id": int64(2), "name": "alice", "lower_name": "bob"}); err == nil {
- t.Fatal("expected unique violation on name")
- }
- if err := tables.Insert("t", Row{"id": int64(2), "name": "bob", "lower_name": "bob"}); err != nil {
- t.Fatalf("distinct values should insert: %v", err)
- }
- }
|