2
0

review_fixes_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. package executor
  2. import (
  3. "fmt"
  4. "sync"
  5. "testing"
  6. "time"
  7. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  8. "github.com/danfragoso/pizzasql-next/pkg/parser"
  9. "github.com/danfragoso/pizzasql-next/pkg/storage"
  10. )
  11. // runWithin fails the test if fn does not return before the deadline. It is used
  12. // to turn a would-be deadlock into a test failure instead of a hung suite.
  13. func runWithin(t *testing.T, d time.Duration, fn func()) {
  14. t.Helper()
  15. done := make(chan struct{})
  16. go func() {
  17. defer close(done)
  18. fn()
  19. }()
  20. select {
  21. case <-done:
  22. case <-time.After(d):
  23. t.Fatalf("operation did not complete within %s (deadlock?)", d)
  24. }
  25. }
  26. func TestFinishDMLPreservesLastInsertRowID(t *testing.T) {
  27. _, schema, table := newTestDB(t)
  28. e := newExec(schema, table)
  29. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)")
  30. execMust(t, e, "INSERT INTO t (v) VALUES ('a')")
  31. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  32. t.Fatalf("last_insert_rowid after insert = %v, want 1", got)
  33. }
  34. execMust(t, e, "UPDATE t SET v = 'b'")
  35. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  36. t.Fatalf("last_insert_rowid after UPDATE = %v, want 1", got)
  37. }
  38. if res := execMust(t, e, "UPDATE t SET v = 'c' RETURNING id"); res.Rows[0][0] != int64(1) {
  39. t.Fatalf("UPDATE RETURNING id = %v", res.Rows[0][0])
  40. }
  41. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  42. t.Fatalf("last_insert_rowid after UPDATE RETURNING = %v, want 1", got)
  43. }
  44. execMust(t, e, "DELETE FROM t")
  45. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  46. t.Fatalf("last_insert_rowid after DELETE = %v, want 1", got)
  47. }
  48. execMust(t, e, "INSERT INTO t (v) VALUES ('d')")
  49. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(2) {
  50. t.Fatalf("last_insert_rowid after second insert = %v, want 2", got)
  51. }
  52. }
  53. func TestUpsertPreservesLastInsertRowID(t *testing.T) {
  54. _, schema, table := newTestDB(t)
  55. e := newExec(schema, table)
  56. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, tag TEXT)")
  57. execMust(t, e, "INSERT INTO t (email, tag) VALUES ('a', 'old')")
  58. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  59. t.Fatalf("insert last_insert_rowid = %v, want 1", got)
  60. }
  61. execMust(t, e, "INSERT INTO t (email, tag) VALUES ('a', 'new') ON CONFLICT (email) DO UPDATE SET tag = excluded.tag")
  62. if got := execMust(t, e, "SELECT last_insert_rowid()").Rows[0][0]; got != int64(1) {
  63. t.Fatalf("upsert-update last_insert_rowid = %v, want 1", got)
  64. }
  65. }
  66. func TestUpsertNonPKUniqueReturningReturnsStoredRow(t *testing.T) {
  67. _, schema, table := newTestDB(t)
  68. e := newExec(schema, table)
  69. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, tag TEXT)")
  70. res := execMust(t, e, "INSERT INTO t (email, tag) VALUES ('a', 'old') RETURNING id, tag")
  71. id := res.Rows[0][0]
  72. res = execMust(t, e, "INSERT INTO t (email, tag) VALUES ('a', 'new') ON CONFLICT (email) DO UPDATE SET tag = excluded.tag RETURNING id, tag")
  73. if res.RowCount != 1 {
  74. t.Fatalf("upsert RETURNING rows = %d, want 1", res.RowCount)
  75. }
  76. if res.Rows[0][0] != id || res.Rows[0][1] != "new" {
  77. t.Fatalf("upsert RETURNING = %v, want [%v new]", res.Rows[0], id)
  78. }
  79. // The candidate's auto-increment id must not have been consumed/returned.
  80. if res.LastInsertID != id {
  81. t.Fatalf("upsert LastInsertID = %v, want preserved %v", res.LastInsertID, id)
  82. }
  83. }
  84. func TestUpdatePrimaryKeyReturningReturnsChangedRow(t *testing.T) {
  85. _, schema, table := newTestDB(t)
  86. e := newExec(schema, table)
  87. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  88. execMust(t, e, "INSERT INTO t VALUES (1, 'a')")
  89. res := execMust(t, e, "UPDATE t SET id = 2 WHERE id = 1 RETURNING id, v")
  90. if res.RowCount != 1 || res.Rows[0][0] != int64(2) || res.Rows[0][1] != "a" {
  91. t.Fatalf("UPDATE pk RETURNING = %v", res.Rows)
  92. }
  93. if rows := execMust(t, e, "SELECT id, v FROM t"); rows.RowCount != 1 || rows.Rows[0][0] != int64(2) {
  94. t.Fatalf("after pk update table = %v (orphan old key?)", rows.Rows)
  95. }
  96. }
  97. func TestUpdatePrimaryKeyInTransaction(t *testing.T) {
  98. _, schema, table := newTestDB(t)
  99. e := newExec(schema, table)
  100. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  101. execMust(t, e, "INSERT INTO t VALUES (1, 'a')")
  102. execMust(t, e, "BEGIN")
  103. execMust(t, e, "UPDATE t SET id = 2 WHERE id = 1")
  104. // The old key must be gone within the transaction overlay too.
  105. if res := execMust(t, e, "SELECT id FROM t"); res.RowCount != 1 || res.Rows[0][0] != int64(2) {
  106. t.Fatalf("in-tx pk update = %v", res.Rows)
  107. }
  108. execMust(t, e, "COMMIT")
  109. res := execMust(t, e, "SELECT id, v FROM t")
  110. if res.RowCount != 1 || res.Rows[0][0] != int64(2) {
  111. t.Fatalf("after commit = %v", res.Rows)
  112. }
  113. }
  114. func TestUpdateDeleteReturningWithSubqueryNoDeadlock(t *testing.T) {
  115. runWithin(t, 10*time.Second, func() {
  116. _, schema, table := newTestDB(t)
  117. e := newExec(schema, table)
  118. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
  119. execMust(t, e, "INSERT INTO t VALUES (1, 'a'), (2, 'b')")
  120. res := execMust(t, e, "UPDATE t SET v = 'x' WHERE id IN (SELECT id FROM t WHERE id = 1) RETURNING id, v")
  121. if res.RowCount != 1 || res.Rows[0][0] != int64(1) || res.Rows[0][1] != "x" {
  122. t.Fatalf("UPDATE ... IN (subquery) RETURNING = %v", res.Rows)
  123. }
  124. res = execMust(t, e, "DELETE FROM t WHERE id IN (SELECT id FROM t WHERE id = 1) RETURNING id")
  125. if res.RowCount != 1 || res.Rows[0][0] != int64(1) {
  126. t.Fatalf("DELETE ... IN (subquery) RETURNING = %v", res.Rows)
  127. }
  128. })
  129. }
  130. func TestUpdateCorrelatedSubqueryInSet(t *testing.T) {
  131. _, schema, table := newTestDB(t)
  132. e := newExec(schema, table)
  133. execMust(t, e, "CREATE TABLE sizes (size_id INTEGER PRIMARY KEY, width INTEGER)")
  134. execMust(t, e, "CREATE TABLE hits (id INTEGER PRIMARY KEY, size_id INTEGER, width INTEGER)")
  135. execMust(t, e, "INSERT INTO sizes VALUES (1, 480), (2, 720)")
  136. execMust(t, e, "INSERT INTO hits (id, size_id) VALUES (1, 1), (2, 2)")
  137. execMust(t, e, "UPDATE hits SET width = (SELECT width FROM sizes WHERE size_id = hits.size_id)")
  138. res := execMust(t, e, "SELECT id, width FROM hits ORDER BY id")
  139. if res.Rows[0][1] != int64(480) || res.Rows[1][1] != int64(720) {
  140. t.Fatalf("correlated SET update = %v", res.Rows)
  141. }
  142. }
  143. func TestUpdateFromWithCTE(t *testing.T) {
  144. _, schema, table := newTestDB(t)
  145. e := newExec(schema, table)
  146. execMust(t, e, "CREATE TABLE users (user_id INTEGER PRIMARY KEY AUTOINCREMENT, site_id INTEGER, access TEXT DEFAULT 'x')")
  147. execMust(t, e, "INSERT INTO users (site_id) VALUES (1), (1), (2)")
  148. // The exact GoatCounter 2021-12-13-2-superuser.sql shape.
  149. execMust(t, e, `WITH x AS (
  150. SELECT count(*) AS count, site_id FROM users GROUP BY site_id
  151. )
  152. UPDATE users SET access = '{"all": "*"}' FROM x
  153. WHERE x.count = 1 AND users.site_id = x.site_id`)
  154. res := execMust(t, e, "SELECT site_id, access FROM users ORDER BY user_id")
  155. if res.Rows[0][1] != "x" || res.Rows[1][1] != "x" {
  156. t.Fatalf("site 1 users should be unchanged, got %v", res.Rows)
  157. }
  158. if res.Rows[2][1] != `{"all": "*"}` {
  159. t.Fatalf("site 2 user should be updated, got %v", res.Rows[2])
  160. }
  161. }
  162. func TestAnalyzeIsSafeNoOp(t *testing.T) {
  163. _, schema, table := newTestDB(t)
  164. e := newExec(schema, table)
  165. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  166. if res := execMust(t, e, "ANALYZE"); res.CommandTag != "ANALYZE" {
  167. t.Fatalf("ANALYZE tag = %q", res.CommandTag)
  168. }
  169. if res := execMust(t, e, "ANALYZE t"); res.CommandTag != "ANALYZE" {
  170. t.Fatalf("ANALYZE t tag = %q", res.CommandTag)
  171. }
  172. }
  173. func TestForeignKeysPragmaNoOp(t *testing.T) {
  174. _, schema, table := newTestDB(t)
  175. e := newExec(schema, table)
  176. if res := execMust(t, e, "PRAGMA foreign_keys = OFF"); res.CommandTag != "PRAGMA" {
  177. t.Fatalf("PRAGMA tag = %q", res.CommandTag)
  178. }
  179. res := execMust(t, e, "PRAGMA foreign_keys")
  180. if res.RowCount != 1 || res.Rows[0][0] != int64(0) {
  181. t.Fatalf("foreign_keys = %v, want 0", res.Rows)
  182. }
  183. if _, err := execSQL(e, "PRAGMA foreign_keys = ON"); err == nil {
  184. t.Fatal("expected enabling unsupported foreign keys to fail")
  185. }
  186. }
  187. func TestIsDistinctFrom(t *testing.T) {
  188. _, schema, table := newTestDB(t)
  189. e := newExec(schema, table)
  190. cases := []struct {
  191. sql string
  192. want int64
  193. }{
  194. {"SELECT 1 IS DISTINCT FROM 2", 1},
  195. {"SELECT 1 IS DISTINCT FROM 1", 0},
  196. {"SELECT NULL IS DISTINCT FROM NULL", 0},
  197. {"SELECT NULL IS DISTINCT FROM 1", 1},
  198. {"SELECT 1 IS NOT DISTINCT FROM 1", 1},
  199. {"SELECT NULL IS NOT DISTINCT FROM NULL", 1},
  200. {"SELECT NULL IS NOT DISTINCT FROM 1", 0},
  201. }
  202. for _, tc := range cases {
  203. got := execMust(t, e, tc.sql).Rows[0][0]
  204. var b int64
  205. if v, ok := got.(bool); ok {
  206. if v {
  207. b = 1
  208. }
  209. } else {
  210. b = got.(int64)
  211. }
  212. if b != tc.want {
  213. t.Errorf("%s = %v, want %d", tc.sql, got, tc.want)
  214. }
  215. }
  216. }
  217. func TestRejectVirtualGeneratedColumn(t *testing.T) {
  218. _, schema, table := newTestDB(t)
  219. e := newExec(schema, table)
  220. if _, err := execSQL(e, "CREATE TABLE a (x INTEGER, y INTEGER GENERATED ALWAYS AS (x + 1))"); err == nil {
  221. t.Fatal("expected implicit VIRTUAL generated column to be rejected")
  222. }
  223. if _, err := execSQL(e, "CREATE TABLE b (x INTEGER, y INTEGER AS (x + 1) VIRTUAL)"); err == nil {
  224. t.Fatal("expected VIRTUAL generated column to be rejected")
  225. }
  226. if _, err := execSQL(e, "CREATE TABLE c (x INTEGER, y INTEGER GENERATED ALWAYS AS (x + 1) STORED)"); err != nil {
  227. t.Fatalf("STORED generated column should be accepted: %v", err)
  228. }
  229. }
  230. func TestRejectUnsupportedIndexExpressions(t *testing.T) {
  231. _, schema, table := newTestDB(t)
  232. e := newExec(schema, table)
  233. execMust(t, e, "CREATE TABLE t (email TEXT)")
  234. for _, sql := range []string{
  235. "CREATE INDEX i1 ON t (random())",
  236. "CREATE INDEX i2 ON t (randomblob(4))",
  237. "CREATE INDEX i3 ON t ((SELECT 1))",
  238. "CREATE INDEX i4 ON t (email || (SELECT 1))",
  239. "CREATE INDEX i5 ON t (no_such_function(email))",
  240. "CREATE INDEX i6 ON t (datetime('now'))",
  241. "CREATE INDEX i7 ON t (lower(missing))",
  242. } {
  243. if _, err := execSQL(e, sql); err == nil {
  244. t.Errorf("%s: expected rejection", sql)
  245. }
  246. }
  247. // Deterministic expressions remain accepted.
  248. if _, err := execSQL(e, "CREATE INDEX iok ON t (lower(email))"); err != nil {
  249. t.Fatalf("lower(email) index should be accepted: %v", err)
  250. }
  251. }
  252. func TestRejectGeneratedColumnWithNonDeterministicExpr(t *testing.T) {
  253. _, schema, table := newTestDB(t)
  254. e := newExec(schema, table)
  255. if _, err := execSQL(e, "CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (random()) STORED)"); err == nil {
  256. t.Fatal("expected non-deterministic generated column to be rejected")
  257. }
  258. if _, err := execSQL(e, "CREATE TABLE t2 (a INTEGER, b INTEGER GENERATED ALWAYS AS ((SELECT 1)) STORED)"); err == nil {
  259. t.Fatal("expected subquery generated column to be rejected")
  260. }
  261. }
  262. func TestNumericConcatFormatting(t *testing.T) {
  263. _, schema, table := newTestDB(t)
  264. e := newExec(schema, table)
  265. cases := map[string]string{
  266. "SELECT 5 || 'px'": "5px",
  267. "SELECT 1.5 - 0.5 || 'px'": "1.0px",
  268. "SELECT 2.5 || ''": "2.5",
  269. "SELECT '↔ ' || 480 || 'px'": "↔ 480px",
  270. "SELECT (SELECT 3.5 - 0.5) || 'x'": "3.0x",
  271. "SELECT CAST(7 AS REAL) || 'x'": "7.0x",
  272. }
  273. for sql, want := range cases {
  274. got := execMust(t, e, sql).Rows[0][0]
  275. if got != want {
  276. t.Errorf("%s = %v, want %q", sql, got, want)
  277. }
  278. }
  279. }
  280. func TestInsertOrReplaceWithExpressionUniqueIndexIsIndexed(t *testing.T) {
  281. _, schema, table := newTestDB(t)
  282. e := newExec(schema, table)
  283. execMust(t, e, "CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT, tag TEXT)")
  284. execMust(t, e, "CREATE UNIQUE INDEX users_email_lower ON users (lower(email))")
  285. for i := 1; i <= 50; i++ {
  286. execMust(t, e, fmt.Sprintf("INSERT INTO users (id, email, tag) VALUES (%d, 'User%d@example.com', 'seed')", i, i))
  287. }
  288. execMust(t, e, "INSERT OR REPLACE INTO users (id, email, tag) VALUES (999, 'user7@example.com', 'replaced')")
  289. res := execMust(t, e, "SELECT id, tag FROM users WHERE lower(email) = 'user7@example.com'")
  290. if res.RowCount != 1 || res.Rows[0][0] != int64(999) || res.Rows[0][1] != "replaced" {
  291. t.Fatalf("replace result = %v", res.Rows)
  292. }
  293. }
  294. func TestCompositeExpressionUniqueReplace(t *testing.T) {
  295. _, schema, table := newTestDB(t)
  296. e := newExec(schema, table)
  297. execMust(t, e, `CREATE TABLE users (
  298. user_id INTEGER PRIMARY KEY AUTOINCREMENT,
  299. site_id INTEGER NOT NULL,
  300. email TEXT NOT NULL
  301. )`)
  302. execMust(t, e, "CREATE UNIQUE INDEX users_site_email ON users(site_id, lower(email))")
  303. execMust(t, e, "INSERT INTO users (site_id, email) VALUES (1, 'A@x.com')")
  304. execMust(t, e, "INSERT OR REPLACE INTO users (site_id, email) VALUES (1, 'a@x.com')")
  305. res := execMust(t, e, "SELECT count(*) FROM users WHERE site_id = 1")
  306. if res.Rows[0][0] != int64(1) {
  307. t.Fatalf("composite expression replace left %v rows", res.Rows[0][0])
  308. }
  309. }
  310. // TestConcurrentExpressionIndex exercises the stateless evaluator under -race.
  311. func TestConcurrentExpressionIndex(t *testing.T) {
  312. _, schema, table := newTestDB(t)
  313. e := newExec(schema, table)
  314. execMust(t, e, "CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
  315. execMust(t, e, "CREATE UNIQUE INDEX users_email_lower ON users (lower(email))")
  316. const workers = 8
  317. const perWorker = 20
  318. var wg sync.WaitGroup
  319. errs := make(chan error, workers)
  320. for w := 0; w < workers; w++ {
  321. wg.Add(1)
  322. go func(w int) {
  323. defer wg.Done()
  324. exec := New(schema, table)
  325. exec.SyncCatalog()
  326. for i := 0; i < perWorker; i++ {
  327. email := fmt.Sprintf("user-%d-%d@example.com", w, i)
  328. sql := fmt.Sprintf("INSERT INTO users (id, email) VALUES (%d, '%s')", w*1000+i+1, email)
  329. stmt, err := parser.New(lexer.New(sql)).Parse()
  330. if err != nil {
  331. errs <- err
  332. return
  333. }
  334. if _, err := exec.Execute(stmt); err != nil {
  335. errs <- err
  336. return
  337. }
  338. }
  339. }(w)
  340. }
  341. wg.Wait()
  342. close(errs)
  343. for err := range errs {
  344. t.Fatalf("concurrent insert failed: %v", err)
  345. }
  346. res := execMust(t, e, "SELECT count(*) FROM users")
  347. if res.Rows[0][0] != int64(workers*perWorker) {
  348. t.Fatalf("row count = %v, want %d", res.Rows[0][0], workers*perWorker)
  349. }
  350. }
  351. func TestExpressionIndexEvaluatorErrorIsNotSwallowed(t *testing.T) {
  352. _, schema, table := newTestDB(t)
  353. e := newExec(schema, table)
  354. execMust(t, e, "CREATE TABLE t (email TEXT)")
  355. execMust(t, e, "CREATE INDEX i ON t (lower(email))")
  356. // Break the evaluator after the index cache is built; a subsequent write
  357. // must surface the evaluator error instead of silently skipping index
  358. // maintenance.
  359. table.SetExpressionEvaluator(func(expression string, row storage.Row) (interface{}, error) {
  360. return nil, fmt.Errorf("boom: %s", expression)
  361. })
  362. if _, err := execSQL(e, "INSERT INTO t (email) VALUES ('a')"); err == nil {
  363. t.Fatal("expected evaluator error to surface on insert")
  364. }
  365. }