review_fixes_test.go 15 KB

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