2
0

optimizations_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package executor
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. "github.com/danfragoso/pizzasql-next/pkg/storage"
  7. )
  8. // newOptExec creates an executor backed by PizzaKV, skipping the test when it
  9. // is unavailable (mirroring the convention in executor_test.go).
  10. func newOptExec(t *testing.T, db string) *Executor {
  11. t.Helper()
  12. pool, err := storage.NewKVPool("localhost:8085", 5, 5*time.Second)
  13. if err != nil {
  14. t.Skipf("PizzaKV not available: %v", err)
  15. }
  16. t.Cleanup(func() { pool.Close() })
  17. schema := storage.NewSchemaManager(pool, db)
  18. table := storage.NewTableManager(pool, schema, db)
  19. return New(schema, table)
  20. }
  21. // intColumn extracts a column of int64 values from a result.
  22. func intColumn(t *testing.T, r *Result, col int) []int64 {
  23. t.Helper()
  24. out := make([]int64, 0, len(r.Rows))
  25. for _, row := range r.Rows {
  26. if col >= len(row) {
  27. t.Fatalf("row too short: %v", row)
  28. }
  29. out = append(out, row[col].(int64))
  30. }
  31. return out
  32. }
  33. // TestTopNOrderByLimitMatchesFullSort verifies bounded top-N execution returns
  34. // exactly the same rows (order included) as a full sort followed by LIMIT/OFFSET.
  35. func TestTopNOrderByLimitMatchesFullSort(t *testing.T) {
  36. exec := newOptExec(t, "test_topn_db")
  37. execSQL(exec, "DROP TABLE IF EXISTS nums")
  38. if _, err := execSQL(exec, "CREATE TABLE nums (id INTEGER PRIMARY KEY, v INTEGER)"); err != nil {
  39. t.Fatalf("create: %v", err)
  40. }
  41. defer execSQL(exec, "DROP TABLE IF EXISTS nums")
  42. const n = 500
  43. for i := 0; i < n; i++ {
  44. // Deterministic permutation of 0..n-1.
  45. v := (i*137 + 41) % n
  46. if _, err := execSQL(exec, fmt.Sprintf("INSERT INTO nums VALUES (%d, %d)", i+1, v)); err != nil {
  47. t.Fatalf("insert: %v", err)
  48. }
  49. }
  50. for _, desc := range []bool{false, true} {
  51. for _, offset := range []int{0, 3, 47, n - 1} {
  52. for _, limit := range []int{1, 2, 17, 100, n + 5} {
  53. dir := "ASC"
  54. if desc {
  55. dir = "DESC"
  56. }
  57. fullRes, err := execSQL(exec, fmt.Sprintf("SELECT v FROM nums ORDER BY v %s", dir))
  58. if err != nil {
  59. t.Fatalf("full: %v", err)
  60. }
  61. full := intColumn(t, fullRes, 0)
  62. q := fmt.Sprintf("SELECT v FROM nums ORDER BY v %s LIMIT %d OFFSET %d", dir, limit, offset)
  63. limRes, err := execSQL(exec, q)
  64. if err != nil {
  65. t.Fatalf("%s: %v", q, err)
  66. }
  67. got := intColumn(t, limRes, 0)
  68. want := sliceRange(full, offset, limit)
  69. if !equalInt64s(got, want) {
  70. t.Fatalf("%s: got %v want %v", q, got, want)
  71. }
  72. }
  73. }
  74. }
  75. }
  76. // TestTopNOrderByLimitTies verifies LIMIT/OFFSET with tied ORDER BY keys returns
  77. // the correct multiset of values even though tie ordering is unspecified.
  78. func TestTopNOrderByLimitTies(t *testing.T) {
  79. exec := newOptExec(t, "test_topn_ties_db")
  80. execSQL(exec, "DROP TABLE IF EXISTS ties")
  81. if _, err := execSQL(exec, "CREATE TABLE ties (id INTEGER PRIMARY KEY, v INTEGER)"); err != nil {
  82. t.Fatalf("create: %v", err)
  83. }
  84. defer execSQL(exec, "DROP TABLE IF EXISTS ties")
  85. // 4 rows with v=1, 2 rows with v=2, 1 row with v=3.
  86. vals := []int{1, 1, 1, 1, 2, 2, 3}
  87. for i, v := range vals {
  88. if _, err := execSQL(exec, fmt.Sprintf("INSERT INTO ties VALUES (%d, %d)", i+1, v)); err != nil {
  89. t.Fatalf("insert: %v", err)
  90. }
  91. }
  92. // LIMIT 3: the three smallest, all v=1.
  93. res, err := execSQL(exec, "SELECT v FROM ties ORDER BY v LIMIT 3")
  94. if err != nil {
  95. t.Fatalf("limit 3: %v", err)
  96. }
  97. got := intColumn(t, res, 0)
  98. if len(got) != 3 || got[0] != 1 || got[1] != 1 || got[2] != 1 {
  99. t.Fatalf("LIMIT 3 got %v, want [1 1 1]", got)
  100. }
  101. // OFFSET 3 LIMIT 3: skip three v=1 rows, then one v=1 + two v=2.
  102. res, err = execSQL(exec, "SELECT v FROM ties ORDER BY v LIMIT 3 OFFSET 3")
  103. if err != nil {
  104. t.Fatalf("offset 3 limit 3: %v", err)
  105. }
  106. got = intColumn(t, res, 0)
  107. if len(got) != 3 || got[0] != 1 || got[1] != 2 || got[2] != 2 {
  108. t.Fatalf("OFFSET 3 LIMIT 3 got %v, want [1 2 2]", got)
  109. }
  110. // OFFSET beyond the ties boundary.
  111. res, err = execSQL(exec, "SELECT v FROM ties ORDER BY v LIMIT 2 OFFSET 5")
  112. if err != nil {
  113. t.Fatalf("offset 5 limit 2: %v", err)
  114. }
  115. got = intColumn(t, res, 0)
  116. if len(got) != 2 || got[0] != 2 || got[1] != 3 {
  117. t.Fatalf("OFFSET 5 LIMIT 2 got %v, want [2 3]", got)
  118. }
  119. }
  120. // TestTopNGroupByOrderLimit verifies the grouped/result-row top-N path.
  121. func TestTopNGroupByOrderLimit(t *testing.T) {
  122. exec := newOptExec(t, "test_topn_group_db")
  123. execSQL(exec, "DROP TABLE IF EXISTS sales")
  124. if _, err := execSQL(exec, "CREATE TABLE sales (id INTEGER PRIMARY KEY, region TEXT, amount INTEGER)"); err != nil {
  125. t.Fatalf("create: %v", err)
  126. }
  127. defer execSQL(exec, "DROP TABLE IF EXISTS sales")
  128. for _, r := range []struct {
  129. id int
  130. region string
  131. amount int
  132. }{
  133. {1, "east", 10}, {2, "west", 20}, {3, "north", 30},
  134. {4, "south", 40}, {5, "east", 50},
  135. } {
  136. if _, err := execSQL(exec, fmt.Sprintf("INSERT INTO sales VALUES (%d, '%s', %d)", r.id, r.region, r.amount)); err != nil {
  137. t.Fatalf("insert: %v", err)
  138. }
  139. }
  140. res, err := execSQL(exec, "SELECT region, COUNT(*) AS c FROM sales GROUP BY region ORDER BY region LIMIT 2")
  141. if err != nil {
  142. t.Fatalf("group topn: %v", err)
  143. }
  144. if len(res.Rows) != 2 {
  145. t.Fatalf("expected 2 rows, got %d: %v", len(res.Rows), res.Rows)
  146. }
  147. if res.Rows[0][0] != "east" || res.Rows[1][0] != "north" {
  148. t.Fatalf("unexpected top-2 regions: %v", res.Rows)
  149. }
  150. if res.Rows[0][1] != int64(2) {
  151. t.Fatalf("unexpected east count: %v", res.Rows[0][1])
  152. }
  153. }
  154. // TestCountFastPathLifecycleAndRollback verifies the exact COUNT(*) fast path
  155. // across writes and transaction rollback.
  156. func TestCountFastPathLifecycleAndRollback(t *testing.T) {
  157. exec := newOptExec(t, "test_count_db")
  158. execSQL(exec, "DROP TABLE IF EXISTS items")
  159. if _, err := execSQL(exec, "CREATE TABLE items (id INTEGER PRIMARY KEY, v TEXT)"); err != nil {
  160. t.Fatalf("create: %v", err)
  161. }
  162. defer execSQL(exec, "DROP TABLE IF EXISTS items")
  163. countStar := func() int64 {
  164. t.Helper()
  165. res, err := execSQL(exec, "SELECT COUNT(*) FROM items")
  166. if err != nil {
  167. t.Fatalf("count: %v", err)
  168. }
  169. return res.Rows[0][0].(int64)
  170. }
  171. if got := countStar(); got != 0 {
  172. t.Fatalf("initial count = %d, want 0", got)
  173. }
  174. for i := 1; i <= 5; i++ {
  175. if _, err := execSQL(exec, fmt.Sprintf("INSERT INTO items VALUES (%d, 'x%d')", i, i)); err != nil {
  176. t.Fatalf("insert: %v", err)
  177. }
  178. }
  179. if got := countStar(); got != 5 {
  180. t.Fatalf("after inserts = %d, want 5", got)
  181. }
  182. // UPDATE keeps the count exact.
  183. if _, err := execSQL(exec, "UPDATE items SET v = 'y' WHERE id = 1"); err != nil {
  184. t.Fatalf("update: %v", err)
  185. }
  186. if got := countStar(); got != 5 {
  187. t.Fatalf("after update = %d, want 5", got)
  188. }
  189. // Bulk insert via INSERT ... SELECT.
  190. if _, err := execSQL(exec, "INSERT INTO items (id, v) SELECT id + 100, v FROM items"); err != nil {
  191. t.Fatalf("insert-select: %v", err)
  192. }
  193. if got := countStar(); got != 10 {
  194. t.Fatalf("after insert-select = %d, want 10", got)
  195. }
  196. // DELETE decrements.
  197. if _, err := execSQL(exec, "DELETE FROM items WHERE id > 100"); err != nil {
  198. t.Fatalf("delete: %v", err)
  199. }
  200. if got := countStar(); got != 5 {
  201. t.Fatalf("after delete = %d, want 5", got)
  202. }
  203. // Transaction rollback restores the exact count.
  204. execSQL(exec, "BEGIN")
  205. if _, err := execSQL(exec, "INSERT INTO items VALUES (999, 'tmp')"); err != nil {
  206. t.Fatalf("tx insert: %v", err)
  207. }
  208. if got := countStar(); got != 6 {
  209. t.Fatalf("inside tx = %d, want 6", got)
  210. }
  211. if _, err := execSQL(exec, "ROLLBACK"); err != nil {
  212. t.Fatalf("rollback: %v", err)
  213. }
  214. if got := countStar(); got != 5 {
  215. t.Fatalf("after rollback = %d, want 5", got)
  216. }
  217. }
  218. // TestCountFastPathRestart verifies a second executor (fresh process state)
  219. // derives the same exact count from durable rows.
  220. func TestCountFastPathRestart(t *testing.T) {
  221. exec := newOptExec(t, "test_count_restart_db")
  222. execSQL(exec, "DROP TABLE IF EXISTS r")
  223. if _, err := execSQL(exec, "CREATE TABLE r (id INTEGER PRIMARY KEY)"); err != nil {
  224. t.Fatalf("create: %v", err)
  225. }
  226. defer execSQL(exec, "DROP TABLE IF EXISTS r")
  227. for i := 1; i <= 7; i++ {
  228. if _, err := execSQL(exec, fmt.Sprintf("INSERT INTO r VALUES (%d)", i)); err != nil {
  229. t.Fatalf("insert: %v", err)
  230. }
  231. }
  232. // A brand-new executor over the same KV.
  233. exec2 := newOptExec(t, "test_count_restart_db")
  234. res, err := execSQL(exec2, "SELECT COUNT(*) FROM r")
  235. if err != nil {
  236. t.Fatalf("count after restart: %v", err)
  237. }
  238. if got := res.Rows[0][0].(int64); got != 7 {
  239. t.Fatalf("count after restart = %d, want 7", got)
  240. }
  241. }
  242. // TestCountFastPathUnsupportedShapesStillCorrect verifies shapes outside the
  243. // fast path fall through to the normal scan and produce correct results.
  244. func TestCountFastPathUnsupportedShapesStillCorrect(t *testing.T) {
  245. exec := newOptExec(t, "test_count_unsupported_db")
  246. execSQL(exec, "DROP TABLE IF EXISTS t2")
  247. execSQL(exec, "DROP TABLE IF EXISTS t1")
  248. if _, err := execSQL(exec, "CREATE TABLE t1 (id INTEGER PRIMARY KEY, x INTEGER)"); err != nil {
  249. t.Fatalf("create t1: %v", err)
  250. }
  251. if _, err := execSQL(exec, "CREATE TABLE t2 (id INTEGER PRIMARY KEY, y INTEGER)"); err != nil {
  252. t.Fatalf("create t2: %v", err)
  253. }
  254. defer execSQL(exec, "DROP TABLE IF EXISTS t2")
  255. defer execSQL(exec, "DROP TABLE IF EXISTS t1")
  256. for i := 1; i <= 4; i++ {
  257. execSQL(exec, fmt.Sprintf("INSERT INTO t1 VALUES (%d, %d)", i, i))
  258. execSQL(exec, fmt.Sprintf("INSERT INTO t2 VALUES (%d, %d)", i, i))
  259. }
  260. cases := []struct {
  261. q string
  262. want int64
  263. }{
  264. {"SELECT COUNT(*) FROM t1 WHERE x > 2", 2},
  265. {"SELECT COUNT(DISTINCT x) FROM t1", 4},
  266. {"SELECT COUNT(*) FROM t1 t1a, t2 t2b", 16},
  267. }
  268. for _, c := range cases {
  269. res, err := execSQL(exec, c.q)
  270. if err != nil {
  271. t.Fatalf("%s: %v", c.q, err)
  272. }
  273. if got := res.Rows[0][0].(int64); got != c.want {
  274. t.Fatalf("%s = %d, want %d", c.q, got, c.want)
  275. }
  276. }
  277. }
  278. func sliceRange(v []int64, offset, limit int) []int64 {
  279. if offset >= len(v) {
  280. return nil
  281. }
  282. end := offset + limit
  283. if end > len(v) {
  284. end = len(v)
  285. }
  286. return v[offset:end]
  287. }
  288. func equalInt64s(a, b []int64) bool {
  289. if len(a) != len(b) {
  290. return false
  291. }
  292. for i := range a {
  293. if a[i] != b[i] {
  294. return false
  295. }
  296. }
  297. return true
  298. }