| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- package executor
- import (
- "strings"
- "sync"
- "testing"
- "time"
- )
- func TestResultLastInsertIDActualRowID(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- res := execMust(t, e, "INSERT INTO t (name) VALUES ('a')")
- if res.LastInsertID != 1 {
- t.Fatalf("first LastInsertID = %d, want 1", res.LastInsertID)
- }
- res = execMust(t, e, "INSERT INTO t (name) VALUES ('b')")
- if res.LastInsertID != 2 {
- t.Fatalf("second LastInsertID = %d, want 2", res.LastInsertID)
- }
- // Never MAX: an explicit high id advances the counter, not a table scan MAX.
- execMust(t, e, "INSERT INTO t (id, name) VALUES (100, 'high')")
- res = execMust(t, e, "INSERT INTO t (name) VALUES ('c')")
- if res.LastInsertID != 101 {
- t.Fatalf("LastInsertID after explicit 100 = %d, want 101", res.LastInsertID)
- }
- }
- func TestLastInsertRowIDFunction(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(0) {
- t.Fatalf("initial last_insert_rowid = %v, want 0", res.Rows[0][0])
- }
- execMust(t, e, "INSERT INTO t (name) VALUES ('a')")
- if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("last_insert_rowid after insert = %v, want 1", res.Rows[0][0])
- }
- }
- func TestChangesAndTotalChangesFunctions(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
- execMust(t, e, "INSERT INTO t VALUES (1)")
- execMust(t, e, "INSERT INTO t VALUES (2)")
- if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("changes() after single insert = %v, want 1", res.Rows[0][0])
- }
- if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(2) {
- t.Fatalf("total_changes() = %v, want 2", res.Rows[0][0])
- }
- execMust(t, e, "UPDATE t SET id = id WHERE id = 1")
- if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("changes() after update = %v, want 1", res.Rows[0][0])
- }
- execMust(t, e, "DELETE FROM t")
- if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(2) {
- t.Fatalf("changes() after delete = %v, want 2", res.Rows[0][0])
- }
- if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(5) {
- t.Fatalf("total_changes() = %v, want 5", res.Rows[0][0])
- }
- }
- func TestLastInsertRowIDHoldsAcrossRollback(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- execMust(t, e, "BEGIN")
- execMust(t, e, "INSERT INTO t (name) VALUES ('x')")
- execMust(t, e, "ROLLBACK")
- if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("last_insert_rowid() after rollback = %v, want 1 (SQLite holds it)", res.Rows[0][0])
- }
- // total_changes() is monotonic: the rolled-back insert still counts.
- if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("total_changes() after rollback = %v, want 1 (not decremented)", res.Rows[0][0])
- }
- if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("changes() after rollback = %v, want 1 (reflects last DML)", res.Rows[0][0])
- }
- }
- func TestTotalChangesMonotonicAcrossSavepointRollback(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- execMust(t, e, "BEGIN")
- execMust(t, e, "INSERT INTO t (name) VALUES ('a')") // total=1
- execMust(t, e, "SAVEPOINT sp1")
- execMust(t, e, "INSERT INTO t (name) VALUES ('b')") // total=2
- execMust(t, e, "INSERT INTO t (name) VALUES ('c')") // total=3
- execMust(t, e, "ROLLBACK TO sp1")
- if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(3) {
- t.Fatalf("total_changes() after ROLLBACK TO = %v, want 3 (monotonic)", res.Rows[0][0])
- }
- execMust(t, e, "INSERT INTO t (name) VALUES ('d')") // total=4
- execMust(t, e, "ROLLBACK")
- if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(4) {
- t.Fatalf("total_changes() after full rollback = %v, want 4 (monotonic)", res.Rows[0][0])
- }
- // changes() reflects the most recent DML statement (INSERT 'd' = 1 row).
- if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("changes() after rollback = %v, want 1", res.Rows[0][0])
- }
- // The entire transaction was rolled back, so no rows survive; total_changes
- // nevertheless still counts every completed DML statement.
- if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(0) {
- t.Fatalf("surviving rows = %v, want 0 (whole transaction rolled back)", res.Rows[0][0])
- }
- }
- func TestLastInsertRowIDMultiRowAndInsertSelect(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- execMust(t, e, "CREATE TABLE src (name TEXT)")
- res := execMust(t, e, "INSERT INTO t (name) VALUES ('a'), ('b')")
- if res.LastInsertID != 2 {
- t.Fatalf("multi-row LastInsertID = %d, want 2 (last row)", res.LastInsertID)
- }
- execMust(t, e, "INSERT INTO src VALUES ('c'), ('d')")
- res = execMust(t, e, "INSERT INTO t (name) SELECT name FROM src")
- if res.LastInsertID != 4 {
- t.Fatalf("insert-select LastInsertID = %d, want 4 (last row)", res.LastInsertID)
- }
- }
- func TestSessionLocalStateIsolation(t *testing.T) {
- _, schema, table := newTestDB(t)
- e1 := newExec(schema, table)
- e2 := newExec(schema, table)
- execMust(t, e1, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- execMust(t, e1, "INSERT INTO t (name) VALUES ('a')")
- execMust(t, e2, "INSERT INTO t (name) VALUES ('b')")
- if res := execMust(t, e1, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("e1 last_insert_rowid = %v, want 1", res.Rows[0][0])
- }
- if res := execMust(t, e2, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(2) {
- t.Fatalf("e2 last_insert_rowid = %v, want 2", res.Rows[0][0])
- }
- if res := execMust(t, e2, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("e2 total_changes = %v, want 1 (session-local)", res.Rows[0][0])
- }
- if res := execMust(t, e1, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
- t.Fatalf("e1 total_changes = %v, want 1 (INSERT only; DDL does not count)", res.Rows[0][0])
- }
- }
- func TestConcurrentSessionRowIDs(t *testing.T) {
- _, schema, table := newTestDB(t)
- execMust(t, newExec(schema, table), "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
- const n = 32
- rowIDs := make([]int64, n)
- var wg sync.WaitGroup
- for i := 0; i < n; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- e := newExec(schema, table)
- res := execMust(t, e, "INSERT INTO t (name) VALUES ('x')")
- rowIDs[i] = res.LastInsertID
- }(i)
- }
- wg.Wait()
- seen := make(map[int64]bool, n)
- for _, id := range rowIDs {
- if id == 0 {
- t.Fatal("an insert returned LastInsertID 0")
- }
- if seen[id] {
- t.Fatalf("duplicate generated rowid %d across concurrent sessions", id)
- }
- seen[id] = true
- }
- }
- func TestUniqueIndexMultiRowUpdateAtomic(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
- execMust(t, e, "CREATE UNIQUE INDEX uq_v ON t (v)")
- execMust(t, e, "INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'c')")
- _, err := execSQL(e, "UPDATE t SET v = 'x'")
- if err == nil {
- t.Fatal("expected unique violation")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- // No partial writes: the first row must not have been changed to 'x'.
- if res := execMust(t, e, "SELECT v FROM t WHERE id = 1"); res.Rows[0][0] != "a" {
- t.Fatalf("partial update applied: row 1 v = %v, want 'a'", res.Rows[0][0])
- }
- }
- func TestUniqueIndexMultiRowInsertAtomic(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
- execMust(t, e, "CREATE UNIQUE INDEX uq_v ON t (v)")
- _, err := execSQL(e, "INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'a')")
- if err == nil {
- t.Fatal("expected unique violation on the third row")
- }
- if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
- t.Fatalf("unexpected error: %v", err)
- }
- if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(0) {
- t.Fatalf("partial insert applied: %v rows present, want 0", res.Rows[0][0])
- }
- }
- func TestUpdateWithScalarSubqueryInTransaction(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, "CREATE TABLE watch (user_id INTEGER, repo_id INTEGER, PRIMARY KEY(user_id, repo_id))")
- execMust(t, e, "CREATE TABLE repository (id INTEGER PRIMARY KEY, num_watches INTEGER)")
- execMust(t, e, "INSERT INTO repository VALUES (1, 0)")
- execMust(t, e, "BEGIN")
- execMust(t, e, "INSERT INTO watch VALUES (1, 1)")
- // A scalar subquery in SET must not deadlock on the session lock while the
- // update callback runs inside an explicit transaction.
- done := make(chan error, 1)
- go func() {
- _, err := execSQL(e, "UPDATE repository SET num_watches = (SELECT COUNT(*) FROM watch WHERE repo_id = 1) WHERE id = 1")
- done <- err
- }()
- select {
- case err := <-done:
- if err != nil {
- t.Fatalf("update with scalar subquery: %v", err)
- }
- case <-time.After(10 * time.Second):
- t.Fatal("UPDATE with scalar subquery deadlocked inside a transaction")
- }
- execMust(t, e, "COMMIT")
- if res := execMust(t, e, "SELECT num_watches FROM repository WHERE id = 1"); res.Rows[0][0] != int64(1) {
- t.Fatalf("num_watches = %v, want 1", res.Rows[0][0])
- }
- }
|