2
0

bounded_scan_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. package executor
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "strings"
  7. "testing"
  8. "github.com/danfragoso/pizzasql-next/pkg/parser"
  9. "github.com/danfragoso/pizzasql-next/pkg/storage"
  10. "github.com/danfragoso/pizzasql-next/pkg/testkv"
  11. )
  12. func TestBoundedScanAndConjunctionLookup(t *testing.T) {
  13. kv := testkv.New(t)
  14. pool := kv.Pool(8)
  15. defer pool.Close()
  16. schema := storage.NewSchemaManager(pool, "bounded")
  17. table := storage.NewTableManager(pool, schema, "bounded")
  18. e := newExec(schema, table)
  19. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)")
  20. var rows []string
  21. for i := 1; i <= 2048; i++ {
  22. rows = append(rows, fmt.Sprintf("(%d,%d)", i, i))
  23. }
  24. execMust(t, e, "INSERT INTO t VALUES "+strings.Join(rows, ","))
  25. full := execMust(t, e, "SELECT id FROM t WHERE v>=0").Rows
  26. for _, tc := range []struct {
  27. sql string
  28. want [][]interface{}
  29. pages int
  30. }{
  31. {"SELECT id FROM t LIMIT 3", full[:3], 1},
  32. {"SELECT id FROM t WHERE v>=0 LIMIT 3 OFFSET 2", full[2:5], 1},
  33. {"SELECT id FROM t LIMIT 0", nil, 0},
  34. {"SELECT id FROM t WHERE v>=0 AND id=1000", [][]interface{}{{int64(1000)}}, 0},
  35. {"SELECT id FROM t WHERE id=1000 AND v<0", nil, 0},
  36. } {
  37. before := kv.RequestCount(10) // PKBFI SCAN_NEXT
  38. got := execMust(t, e, tc.sql).Rows
  39. if len(got) != len(tc.want) || (len(got) > 0 && !reflect.DeepEqual(got, tc.want)) {
  40. t.Fatalf("%s: got %v want %v", tc.sql, got, tc.want)
  41. }
  42. if pages := kv.RequestCount(10) - before; pages != tc.pages {
  43. t.Fatalf("%s: scanned %d pages want %d", tc.sql, pages, tc.pages)
  44. }
  45. }
  46. execMust(t, e, "CREATE INDEX t_v ON t(v)")
  47. before := kv.RequestCount(10)
  48. if got := execMust(t, e, "SELECT id FROM t WHERE id>0 AND v=1000").Rows; !reflect.DeepEqual(got, [][]interface{}{{int64(1000)}}) {
  49. t.Fatal(got)
  50. }
  51. if kv.RequestCount(10) != before {
  52. t.Fatal("indexed conjunct fell back to scan")
  53. }
  54. execMust(t, e, "BEGIN")
  55. execMust(t, e, "INSERT INTO t VALUES (9999,9999)")
  56. if got := execMust(t, e, "SELECT id FROM t WHERE v>9000 LIMIT 1").Rows; !reflect.DeepEqual(got, [][]interface{}{{int64(9999)}}) {
  57. t.Fatalf("bounded scan lost transaction overlay: %v", got)
  58. }
  59. execMust(t, e, "SELECT id FROM t LIMIT 1")
  60. other := newExec(schema, table)
  61. execMust(t, other, "UPDATE t SET v=0 WHERE id=2048")
  62. if _, err := execSQL(e, "COMMIT"); !errors.Is(err, storage.ErrSerialization) {
  63. t.Fatalf("limited transaction lost scan validation: %v", err)
  64. }
  65. }
  66. func TestScanLimitRejectsUnsafeShapes(t *testing.T) {
  67. for _, sql := range []string{
  68. "SELECT DISTINCT v FROM t LIMIT 1",
  69. "SELECT COUNT(*) FROM t LIMIT 1",
  70. "SELECT v FROM t GROUP BY v LIMIT 1",
  71. "SELECT v FROM t ORDER BY v LIMIT 1",
  72. "SELECT ROW_NUMBER() OVER (ORDER BY v) FROM t LIMIT 1",
  73. "SELECT (SELECT 1) FROM t LIMIT 1",
  74. "SELECT v FROM t LIMIT -1",
  75. "SELECT v FROM t LIMIT 1+1",
  76. } {
  77. stmt := parse(t, sql)
  78. // Parsing is enough: eligibility must not execute expressions or scan data.
  79. if _, ok := scanLimit(stmt.(*parser.SelectStmt)); ok {
  80. t.Fatalf("unsafe bound for %s", sql)
  81. }
  82. }
  83. }