2
0

arrays_test.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package analyzer
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/danfragoso/pizzasql-next/pkg/parser"
  6. )
  7. // arrayCatalog describes an array-typed table so expression analysis sees the
  8. // same declared types the executor resolves from storage.
  9. func arrayCatalog() *Catalog {
  10. catalog := NewCatalog()
  11. catalog.CreateTable(&TableInfo{
  12. Name: "documents",
  13. Columns: []ColumnInfo{
  14. {Name: "id", Type: TypeInteger, PrimaryKey: true},
  15. {Name: "tags", Type: TypeFromName("TEXT[]"), Nullable: true, Array: true},
  16. {Name: "nums", Type: TypeFromName("INTEGER[]"), Nullable: true, Array: true},
  17. },
  18. })
  19. return catalog
  20. }
  21. func TestArrayTypeNameKeepsElementAffinity(t *testing.T) {
  22. cases := map[string]Type{
  23. "TEXT[]": TypeText,
  24. "VARCHAR(10)[]": TypeText,
  25. "INTEGER[]": TypeInteger,
  26. "BIGINT[]": TypeInteger,
  27. "BOOLEAN[]": TypeBoolean,
  28. "DOUBLE PRECISION[]": TypeReal,
  29. "NUMERIC[]": TypeNumeric,
  30. }
  31. for name, want := range cases {
  32. if got := TypeFromName(name); got != want {
  33. t.Errorf("TypeFromName(%q) = %v, want %v", name, got, want)
  34. }
  35. }
  36. }
  37. func TestArrayColumnDDLAnalyzes(t *testing.T) {
  38. a := New(setupCatalog())
  39. if err := a.Analyze(parse(t, `CREATE TABLE tagged (id INTEGER PRIMARY KEY, tags TEXT[], nums INTEGER[])`)); err != nil {
  40. t.Fatalf("array column DDL: %v", err)
  41. }
  42. if err := a.Analyze(parse(t, `ALTER TABLE tagged ADD COLUMN flags BOOLEAN[]`)); err != nil {
  43. t.Fatalf("ALTER TABLE ADD array column: %v", err)
  44. }
  45. }
  46. func TestArrayExpressionsAnalyze(t *testing.T) {
  47. a := New(arrayCatalog())
  48. for _, sql := range []string{
  49. `SELECT tags FROM documents WHERE 'go' = ANY(tags)`,
  50. `SELECT tags FROM documents WHERE 'go' <> ALL(tags)`,
  51. `SELECT nums FROM documents WHERE id = ANY(ARRAY[1,2])`,
  52. `SELECT tags[1] FROM documents WHERE tags[1] = 'go'`,
  53. `SELECT array_length(tags, 1) FROM documents`,
  54. `SELECT ARRAY['a','b'] FROM documents`,
  55. `SELECT '{1,2}'::INTEGER[] FROM documents`,
  56. `SELECT nums FROM documents WHERE nums = '{3,4}'`,
  57. `SELECT nums FROM documents WHERE '{3,4}' = nums`,
  58. `SELECT nums FROM documents WHERE nums <> '{3,4}'`,
  59. } {
  60. if err := a.Analyze(parse(t, sql)); err != nil {
  61. t.Errorf("analyze %q: %v", sql, err)
  62. }
  63. }
  64. }
  65. func TestArrayExpressionRejections(t *testing.T) {
  66. a := New(arrayCatalog())
  67. cases := []struct {
  68. sql string
  69. want string
  70. }{
  71. {`SELECT missing[1] FROM documents`, "column not found"},
  72. {`SELECT tags['x'] FROM documents`, "array subscript must be an integer expression"},
  73. // A text literal is only coercible to an array side, not to a scalar
  74. // integer column.
  75. {`SELECT id FROM documents WHERE id = '{3,4}'`, "cannot compare INTEGER with TEXT"},
  76. // An array column still does not compare with a scalar number.
  77. {`SELECT nums FROM documents WHERE nums = 5`, "cannot compare INTEGER with INTEGER"},
  78. }
  79. for _, tc := range cases {
  80. err := a.Analyze(parse(t, tc.sql))
  81. if err == nil {
  82. t.Errorf("%q analyzed, want an error", tc.sql)
  83. continue
  84. }
  85. if !strings.Contains(err.Error(), tc.want) {
  86. t.Errorf("%q error = %v, want it to mention %q", tc.sql, err, tc.want)
  87. }
  88. }
  89. }
  90. func TestUnnestFromDefinesRelation(t *testing.T) {
  91. a := New(arrayCatalog())
  92. stmt := parse(t, `SELECT u.unnest FROM unnest('{1,2}'::INTEGER[]) AS u`).(*parser.SelectStmt)
  93. if err := a.Analyze(stmt); err != nil {
  94. t.Fatalf("unnest FROM analysis: %v", err)
  95. }
  96. }