session_state_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. package executor
  2. import (
  3. "strings"
  4. "sync"
  5. "testing"
  6. "time"
  7. )
  8. func TestResultLastInsertIDActualRowID(t *testing.T) {
  9. _, schema, table := newTestDB(t)
  10. e := newExec(schema, table)
  11. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  12. res := execMust(t, e, "INSERT INTO t (name) VALUES ('a')")
  13. if res.LastInsertID != 1 {
  14. t.Fatalf("first LastInsertID = %d, want 1", res.LastInsertID)
  15. }
  16. res = execMust(t, e, "INSERT INTO t (name) VALUES ('b')")
  17. if res.LastInsertID != 2 {
  18. t.Fatalf("second LastInsertID = %d, want 2", res.LastInsertID)
  19. }
  20. // Never MAX: an explicit high id advances the counter, not a table scan MAX.
  21. execMust(t, e, "INSERT INTO t (id, name) VALUES (100, 'high')")
  22. res = execMust(t, e, "INSERT INTO t (name) VALUES ('c')")
  23. if res.LastInsertID != 101 {
  24. t.Fatalf("LastInsertID after explicit 100 = %d, want 101", res.LastInsertID)
  25. }
  26. }
  27. func TestLastInsertRowIDFunction(t *testing.T) {
  28. _, schema, table := newTestDB(t)
  29. e := newExec(schema, table)
  30. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  31. if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(0) {
  32. t.Fatalf("initial last_insert_rowid = %v, want 0", res.Rows[0][0])
  33. }
  34. execMust(t, e, "INSERT INTO t (name) VALUES ('a')")
  35. if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
  36. t.Fatalf("last_insert_rowid after insert = %v, want 1", res.Rows[0][0])
  37. }
  38. }
  39. func TestChangesAndTotalChangesFunctions(t *testing.T) {
  40. _, schema, table := newTestDB(t)
  41. e := newExec(schema, table)
  42. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  43. execMust(t, e, "INSERT INTO t VALUES (1)")
  44. execMust(t, e, "INSERT INTO t VALUES (2)")
  45. if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
  46. t.Fatalf("changes() after single insert = %v, want 1", res.Rows[0][0])
  47. }
  48. if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(2) {
  49. t.Fatalf("total_changes() = %v, want 2", res.Rows[0][0])
  50. }
  51. execMust(t, e, "UPDATE t SET id = id WHERE id = 1")
  52. if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
  53. t.Fatalf("changes() after update = %v, want 1", res.Rows[0][0])
  54. }
  55. execMust(t, e, "DELETE FROM t")
  56. if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(2) {
  57. t.Fatalf("changes() after delete = %v, want 2", res.Rows[0][0])
  58. }
  59. if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(5) {
  60. t.Fatalf("total_changes() = %v, want 5", res.Rows[0][0])
  61. }
  62. }
  63. func TestLastInsertRowIDHoldsAcrossRollback(t *testing.T) {
  64. _, schema, table := newTestDB(t)
  65. e := newExec(schema, table)
  66. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  67. execMust(t, e, "BEGIN")
  68. execMust(t, e, "INSERT INTO t (name) VALUES ('x')")
  69. execMust(t, e, "ROLLBACK")
  70. if res := execMust(t, e, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
  71. t.Fatalf("last_insert_rowid() after rollback = %v, want 1 (SQLite holds it)", res.Rows[0][0])
  72. }
  73. // total_changes() is monotonic: the rolled-back insert still counts.
  74. if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
  75. t.Fatalf("total_changes() after rollback = %v, want 1 (not decremented)", res.Rows[0][0])
  76. }
  77. if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
  78. t.Fatalf("changes() after rollback = %v, want 1 (reflects last DML)", res.Rows[0][0])
  79. }
  80. }
  81. func TestTotalChangesMonotonicAcrossSavepointRollback(t *testing.T) {
  82. _, schema, table := newTestDB(t)
  83. e := newExec(schema, table)
  84. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  85. execMust(t, e, "BEGIN")
  86. execMust(t, e, "INSERT INTO t (name) VALUES ('a')") // total=1
  87. execMust(t, e, "SAVEPOINT sp1")
  88. execMust(t, e, "INSERT INTO t (name) VALUES ('b')") // total=2
  89. execMust(t, e, "INSERT INTO t (name) VALUES ('c')") // total=3
  90. execMust(t, e, "ROLLBACK TO sp1")
  91. if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(3) {
  92. t.Fatalf("total_changes() after ROLLBACK TO = %v, want 3 (monotonic)", res.Rows[0][0])
  93. }
  94. execMust(t, e, "INSERT INTO t (name) VALUES ('d')") // total=4
  95. execMust(t, e, "ROLLBACK")
  96. if res := execMust(t, e, "SELECT total_changes()"); res.Rows[0][0] != int64(4) {
  97. t.Fatalf("total_changes() after full rollback = %v, want 4 (monotonic)", res.Rows[0][0])
  98. }
  99. // changes() reflects the most recent DML statement (INSERT 'd' = 1 row).
  100. if res := execMust(t, e, "SELECT changes()"); res.Rows[0][0] != int64(1) {
  101. t.Fatalf("changes() after rollback = %v, want 1", res.Rows[0][0])
  102. }
  103. // The entire transaction was rolled back, so no rows survive; total_changes
  104. // nevertheless still counts every completed DML statement.
  105. if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(0) {
  106. t.Fatalf("surviving rows = %v, want 0 (whole transaction rolled back)", res.Rows[0][0])
  107. }
  108. }
  109. func TestLastInsertRowIDMultiRowAndInsertSelect(t *testing.T) {
  110. _, schema, table := newTestDB(t)
  111. e := newExec(schema, table)
  112. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  113. execMust(t, e, "CREATE TABLE src (name TEXT)")
  114. res := execMust(t, e, "INSERT INTO t (name) VALUES ('a'), ('b')")
  115. if res.LastInsertID != 2 {
  116. t.Fatalf("multi-row LastInsertID = %d, want 2 (last row)", res.LastInsertID)
  117. }
  118. execMust(t, e, "INSERT INTO src VALUES ('c'), ('d')")
  119. res = execMust(t, e, "INSERT INTO t (name) SELECT name FROM src")
  120. if res.LastInsertID != 4 {
  121. t.Fatalf("insert-select LastInsertID = %d, want 4 (last row)", res.LastInsertID)
  122. }
  123. }
  124. func TestSessionLocalStateIsolation(t *testing.T) {
  125. _, schema, table := newTestDB(t)
  126. e1 := newExec(schema, table)
  127. e2 := newExec(schema, table)
  128. execMust(t, e1, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  129. execMust(t, e1, "INSERT INTO t (name) VALUES ('a')")
  130. execMust(t, e2, "INSERT INTO t (name) VALUES ('b')")
  131. if res := execMust(t, e1, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(1) {
  132. t.Fatalf("e1 last_insert_rowid = %v, want 1", res.Rows[0][0])
  133. }
  134. if res := execMust(t, e2, "SELECT last_insert_rowid()"); res.Rows[0][0] != int64(2) {
  135. t.Fatalf("e2 last_insert_rowid = %v, want 2", res.Rows[0][0])
  136. }
  137. if res := execMust(t, e2, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
  138. t.Fatalf("e2 total_changes = %v, want 1 (session-local)", res.Rows[0][0])
  139. }
  140. if res := execMust(t, e1, "SELECT total_changes()"); res.Rows[0][0] != int64(1) {
  141. t.Fatalf("e1 total_changes = %v, want 1 (INSERT only; DDL does not count)", res.Rows[0][0])
  142. }
  143. }
  144. func TestConcurrentSessionRowIDs(t *testing.T) {
  145. _, schema, table := newTestDB(t)
  146. execMust(t, newExec(schema, table), "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
  147. const n = 32
  148. rowIDs := make([]int64, n)
  149. var wg sync.WaitGroup
  150. for i := 0; i < n; i++ {
  151. wg.Add(1)
  152. go func(i int) {
  153. defer wg.Done()
  154. e := newExec(schema, table)
  155. res := execMust(t, e, "INSERT INTO t (name) VALUES ('x')")
  156. rowIDs[i] = res.LastInsertID
  157. }(i)
  158. }
  159. wg.Wait()
  160. seen := make(map[int64]bool, n)
  161. for _, id := range rowIDs {
  162. if id == 0 {
  163. t.Fatal("an insert returned LastInsertID 0")
  164. }
  165. if seen[id] {
  166. t.Fatalf("duplicate generated rowid %d across concurrent sessions", id)
  167. }
  168. seen[id] = true
  169. }
  170. }
  171. func TestUniqueIndexMultiRowUpdateAtomic(t *testing.T) {
  172. _, schema, table := newTestDB(t)
  173. e := newExec(schema, table)
  174. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  175. execMust(t, e, "CREATE UNIQUE INDEX uq_v ON t (v)")
  176. execMust(t, e, "INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'c')")
  177. _, err := execSQL(e, "UPDATE t SET v = 'x'")
  178. if err == nil {
  179. t.Fatal("expected unique violation")
  180. }
  181. if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  182. t.Fatalf("unexpected error: %v", err)
  183. }
  184. // No partial writes: the first row must not have been changed to 'x'.
  185. if res := execMust(t, e, "SELECT v FROM t WHERE id = 1"); res.Rows[0][0] != "a" {
  186. t.Fatalf("partial update applied: row 1 v = %v, want 'a'", res.Rows[0][0])
  187. }
  188. }
  189. func TestUniqueIndexMultiRowInsertAtomic(t *testing.T) {
  190. _, schema, table := newTestDB(t)
  191. e := newExec(schema, table)
  192. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  193. execMust(t, e, "CREATE UNIQUE INDEX uq_v ON t (v)")
  194. _, err := execSQL(e, "INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'a')")
  195. if err == nil {
  196. t.Fatal("expected unique violation on the third row")
  197. }
  198. if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
  199. t.Fatalf("unexpected error: %v", err)
  200. }
  201. if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(0) {
  202. t.Fatalf("partial insert applied: %v rows present, want 0", res.Rows[0][0])
  203. }
  204. }
  205. func TestPrimaryKeyMultiRowInsertAtomic(t *testing.T) {
  206. _, schema, table := newTestDB(t)
  207. e := newExec(schema, table)
  208. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  209. _, err := execSQL(e, "INSERT INTO t VALUES (1, 'a'), (1, 'b')")
  210. if err == nil {
  211. t.Fatal("expected primary-key violation on the second row")
  212. }
  213. if res := execMust(t, e, "SELECT count(*) FROM t"); res.Rows[0][0] != int64(0) {
  214. t.Fatalf("partial insert applied: %v rows present, want 0", res.Rows[0][0])
  215. }
  216. }
  217. func TestUpdateWithScalarSubqueryInTransaction(t *testing.T) {
  218. _, schema, table := newTestDB(t)
  219. e := newExec(schema, table)
  220. execMust(t, e, "CREATE TABLE watch (user_id INTEGER, repo_id INTEGER, PRIMARY KEY(user_id, repo_id))")
  221. execMust(t, e, "CREATE TABLE repository (id INTEGER PRIMARY KEY, num_watches INTEGER)")
  222. execMust(t, e, "INSERT INTO repository VALUES (1, 0)")
  223. execMust(t, e, "BEGIN")
  224. execMust(t, e, "INSERT INTO watch VALUES (1, 1)")
  225. // A scalar subquery in SET must not deadlock on the session lock while the
  226. // update callback runs inside an explicit transaction.
  227. done := make(chan error, 1)
  228. go func() {
  229. _, err := execSQL(e, "UPDATE repository SET num_watches = (SELECT COUNT(*) FROM watch WHERE repo_id = 1) WHERE id = 1")
  230. done <- err
  231. }()
  232. select {
  233. case err := <-done:
  234. if err != nil {
  235. t.Fatalf("update with scalar subquery: %v", err)
  236. }
  237. case <-time.After(10 * time.Second):
  238. t.Fatal("UPDATE with scalar subquery deadlocked inside a transaction")
  239. }
  240. execMust(t, e, "COMMIT")
  241. if res := execMust(t, e, "SELECT num_watches FROM repository WHERE id = 1"); res.Rows[0][0] != int64(1) {
  242. t.Fatalf("num_watches = %v, want 1", res.Rows[0][0])
  243. }
  244. }