arrays.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. package executor
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  7. "github.com/danfragoso/pizzasql-next/pkg/parser"
  8. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  9. "github.com/danfragoso/pizzasql-next/pkg/storage"
  10. )
  11. // Array values are carried as their canonical PostgreSQL array-literal text.
  12. // Storing the text keeps the row codec unchanged (a string is a string), makes
  13. // NULL and the empty array distinguishable (nil vs "{}"), and gives reloads a
  14. // stable encoding: every write path canonicalizes before storage, so equal
  15. // arrays always encode to identical bytes.
  16. //
  17. // The canonical form uses unquoted elements when the element text is unambiguous
  18. // and double-quoted (backslash-escaped) elements otherwise. Element text is
  19. // canonicalized per declared element type: integers to decimal, reals to Go's
  20. // shortest round-trip form, booleans to t/f.
  21. // arrayElementKind classifies an array element type for canonicalization.
  22. type arrayElementKind int
  23. const (
  24. arrayElementText arrayElementKind = iota
  25. arrayElementInteger
  26. arrayElementReal
  27. arrayElementBoolean
  28. )
  29. // arrayElementType reports the base element type of an array type name such as
  30. // "INTEGER[]". It reports false for non-array type names.
  31. func arrayElementType(typeName string) (string, bool) {
  32. upper := strings.ToUpper(strings.TrimSpace(typeName))
  33. if !strings.HasSuffix(upper, "[]") {
  34. return "", false
  35. }
  36. return strings.TrimSpace(strings.TrimSuffix(upper, "[]")), true
  37. }
  38. // arrayElementKindOf reports the canonicalization kind for an array type name.
  39. func arrayElementKindOf(typeName string) (arrayElementKind, error) {
  40. element, ok := arrayElementType(typeName)
  41. if !ok {
  42. return arrayElementText, fmt.Errorf("%s is not an array type", typeName)
  43. }
  44. return arrayElementKindForName(element)
  45. }
  46. // arrayElementKindForName classifies a scalar element type name.
  47. func arrayElementKindForName(name string) (arrayElementKind, error) {
  48. switch strings.ToUpper(strings.TrimSpace(name)) {
  49. case "TEXT", "VARCHAR", "CHAR", "CHARACTER", "CLOB", "NCHAR", "NVARCHAR", "UUID":
  50. return arrayElementText, nil
  51. case "INTEGER", "INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT":
  52. return arrayElementInteger, nil
  53. case "REAL", "FLOAT", "DOUBLE", "NUMERIC", "DECIMAL":
  54. return arrayElementReal, nil
  55. case "BOOLEAN", "BOOL":
  56. return arrayElementBoolean, nil
  57. default:
  58. return arrayElementText, pgerror.Unsupported("arrays of %s are not supported; supported element types are TEXT, VARCHAR, INTEGER, BIGINT, SMALLINT, BOOLEAN, REAL, DOUBLE PRECISION, and NUMERIC", strings.ToUpper(name))
  59. }
  60. }
  61. // parseArrayLiteral splits PostgreSQL array-literal text into element strings.
  62. // A nil element is SQL NULL. Nested braces (multidimensional arrays) are
  63. // rejected, as is malformed quoting.
  64. func parseArrayLiteral(text string) ([]*string, error) {
  65. s := strings.TrimSpace(text)
  66. if len(s) < 2 || s[0] != '{' || s[len(s)-1] != '}' {
  67. return nil, invalidArrayInput(text, "array text must be enclosed in {}")
  68. }
  69. inner := s[1 : len(s)-1]
  70. if strings.TrimSpace(inner) == "" {
  71. return nil, nil
  72. }
  73. var elements []*string
  74. i := 0
  75. for {
  76. for i < len(inner) && isArraySpace(inner[i]) {
  77. i++
  78. }
  79. if i >= len(inner) {
  80. return nil, invalidArrayInput(text, "missing } after comma")
  81. }
  82. var value *string
  83. if inner[i] == '"' {
  84. i++
  85. var b strings.Builder
  86. closed := false
  87. for i < len(inner) {
  88. c := inner[i]
  89. if c == '\\' {
  90. if i+1 >= len(inner) {
  91. return nil, invalidArrayInput(text, "trailing backslash in quoted element")
  92. }
  93. b.WriteByte(inner[i+1])
  94. i += 2
  95. continue
  96. }
  97. if c == '"' {
  98. i++
  99. closed = true
  100. break
  101. }
  102. b.WriteByte(c)
  103. i++
  104. }
  105. if !closed {
  106. return nil, invalidArrayInput(text, "unterminated quoted element")
  107. }
  108. quoted := b.String()
  109. value = &quoted
  110. for i < len(inner) && isArraySpace(inner[i]) {
  111. i++
  112. }
  113. } else {
  114. if inner[i] == '{' {
  115. return nil, pgerror.Unsupported("multidimensional arrays are not supported; array text must be one-dimensional")
  116. }
  117. start := i
  118. for i < len(inner) && inner[i] != ',' {
  119. if inner[i] == '{' || inner[i] == '}' || inner[i] == '"' {
  120. return nil, invalidArrayInput(text, "malformed array element")
  121. }
  122. i++
  123. }
  124. raw := strings.TrimSpace(inner[start:i])
  125. if !strings.EqualFold(raw, "NULL") {
  126. value = &raw
  127. }
  128. }
  129. elements = append(elements, value)
  130. if i >= len(inner) {
  131. return elements, nil
  132. }
  133. if inner[i] != ',' {
  134. return nil, invalidArrayInput(text, "expected , between elements")
  135. }
  136. i++
  137. if i >= len(inner) {
  138. return nil, invalidArrayInput(text, "trailing comma")
  139. }
  140. }
  141. }
  142. func isArraySpace(c byte) bool {
  143. return c == ' ' || c == '\t' || c == '\n' || c == '\r'
  144. }
  145. // invalidArrayInput reports malformed array text as a client input error.
  146. func invalidArrayInput(text, reason string) error {
  147. return pgerror.InvalidText("invalid array input %q: %s", text, reason)
  148. }
  149. // formatArrayLiteral renders elements in canonical array text.
  150. func formatArrayLiteral(elements []*string) string {
  151. var b strings.Builder
  152. b.WriteByte('{')
  153. for i, element := range elements {
  154. if i > 0 {
  155. b.WriteByte(',')
  156. }
  157. if element == nil {
  158. b.WriteString("NULL")
  159. continue
  160. }
  161. b.WriteString(formatArrayElementText(*element))
  162. }
  163. b.WriteByte('}')
  164. return b.String()
  165. }
  166. // formatArrayElementText quotes an element when leaving it bare would change its
  167. // meaning: empty text, the NULL keyword, or characters that delimit elements.
  168. func formatArrayElementText(text string) string {
  169. needsQuote := text == "" || strings.EqualFold(text, "NULL")
  170. for i := 0; i < len(text) && !needsQuote; i++ {
  171. switch text[i] {
  172. case '{', '}', ',', '"', '\\', ' ', '\t', '\n', '\r':
  173. needsQuote = true
  174. }
  175. }
  176. if !needsQuote {
  177. return text
  178. }
  179. var b strings.Builder
  180. b.WriteByte('"')
  181. for i := 0; i < len(text); i++ {
  182. if text[i] == '"' || text[i] == '\\' {
  183. b.WriteByte('\\')
  184. }
  185. b.WriteByte(text[i])
  186. }
  187. b.WriteByte('"')
  188. return b.String()
  189. }
  190. // CanonicalizeArrayText validates PostgreSQL array text for an array type and
  191. // returns its canonical encoding. The protocol layer uses it to reject a
  192. // malformed bound array parameter at Bind time instead of waiting for a row to
  193. // evaluate the predicate.
  194. func CanonicalizeArrayText(text, typeName string) (string, error) {
  195. return canonicalArrayText(text, typeName)
  196. }
  197. // canonicalArrayText parses array text and re-encodes it for the given array
  198. // type, converting each element to its canonical spelling.
  199. func canonicalArrayText(text, typeName string) (string, error) {
  200. kind, err := arrayElementKindOf(typeName)
  201. if err != nil {
  202. return "", err
  203. }
  204. elements, err := parseArrayLiteral(text)
  205. if err != nil {
  206. return "", err
  207. }
  208. canonical := make([]*string, len(elements))
  209. for i, element := range elements {
  210. value, elementErr := canonicalArrayElement(element, kind, typeName)
  211. if elementErr != nil {
  212. return "", elementErr
  213. }
  214. canonical[i] = value
  215. }
  216. return formatArrayLiteral(canonical), nil
  217. }
  218. // canonicalArrayElement converts one element to its canonical text. Element
  219. // types the engine does not carry exactly (integer widths, floating point) are
  220. // parsed and re-formatted so equivalent inputs encode identically.
  221. func canonicalArrayElement(element *string, kind arrayElementKind, typeName string) (*string, error) {
  222. if element == nil {
  223. return nil, nil
  224. }
  225. raw := *element
  226. switch kind {
  227. case arrayElementInteger:
  228. n, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
  229. if err != nil {
  230. return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
  231. }
  232. canonical := strconv.FormatInt(n, 10)
  233. return &canonical, nil
  234. case arrayElementReal:
  235. f, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
  236. if err != nil {
  237. return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
  238. }
  239. canonical := strconv.FormatFloat(f, 'g', -1, 64)
  240. return &canonical, nil
  241. case arrayElementBoolean:
  242. value, ok := parseArrayBoolean(raw)
  243. if !ok {
  244. return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
  245. }
  246. canonical := "f"
  247. if value {
  248. canonical = "t"
  249. }
  250. return &canonical, nil
  251. default:
  252. canonical := raw
  253. return &canonical, nil
  254. }
  255. }
  256. // parseArrayBoolean accepts the boolean spellings PostgreSQL accepts in a text
  257. // array element and reports the value.
  258. func parseArrayBoolean(raw string) (bool, bool) {
  259. switch strings.ToLower(strings.TrimSpace(raw)) {
  260. case "t", "true", "1", "y", "yes", "on":
  261. return true, true
  262. case "f", "false", "0", "n", "no", "off":
  263. return false, true
  264. default:
  265. return false, false
  266. }
  267. }
  268. // arrayElementValue converts canonical element text to the Go value the engine
  269. // uses for that element type. Text elements stay strings.
  270. func arrayElementValue(element *string, elementType string) (interface{}, error) {
  271. if element == nil {
  272. return nil, nil
  273. }
  274. kind, err := arrayElementKindForName(elementType)
  275. if err != nil {
  276. return nil, err
  277. }
  278. raw := *element
  279. switch kind {
  280. case arrayElementInteger:
  281. return strconv.ParseInt(raw, 10, 64)
  282. case arrayElementReal:
  283. return strconv.ParseFloat(raw, 64)
  284. case arrayElementBoolean:
  285. value, _ := parseArrayBoolean(raw)
  286. return value, nil
  287. default:
  288. return raw, nil
  289. }
  290. }
  291. // arrayValueElement renders one evaluated expression value as a canonical array
  292. // element. Array nesting and non-scalar values are rejected.
  293. func arrayValueElement(value interface{}) (*string, error) {
  294. switch v := value.(type) {
  295. case nil:
  296. return nil, nil
  297. case bool:
  298. text := "f"
  299. if v {
  300. text = "t"
  301. }
  302. return &text, nil
  303. case int64:
  304. text := strconv.FormatInt(v, 10)
  305. return &text, nil
  306. case int:
  307. text := strconv.FormatInt(int64(v), 10)
  308. return &text, nil
  309. case float64:
  310. text := strconv.FormatFloat(v, 'g', -1, 64)
  311. return &text, nil
  312. case string:
  313. return &v, nil
  314. case []byte:
  315. return nil, pgerror.Unsupported("arrays of BLOB are not supported")
  316. default:
  317. return nil, pgerror.Unsupported("ARRAY[...] element type %T is not supported", value)
  318. }
  319. }
  320. // evalArrayExpr evaluates the ARRAY[...] constructor into canonical array text.
  321. func (e *Executor) evalArrayExpr(expr *parser.ArrayExpr, row storage.Row) (interface{}, error) {
  322. elements := make([]*string, len(expr.Elements))
  323. for i, element := range expr.Elements {
  324. value, err := e.evalExpr(element, row)
  325. if err != nil {
  326. return nil, err
  327. }
  328. text, err := arrayValueElement(value)
  329. if err != nil {
  330. return nil, fmt.Errorf("ARRAY element %d: %w", i+1, err)
  331. }
  332. elements[i] = text
  333. }
  334. return formatArrayLiteral(elements), nil
  335. }
  336. // evalSubscriptExpr evaluates arr[index] with PostgreSQL's 1-based indexing. An
  337. // out-of-range subscript yields NULL, and a NULL array, index, or element yields
  338. // NULL.
  339. func (e *Executor) evalSubscriptExpr(expr *parser.SubscriptExpr, row storage.Row) (interface{}, error) {
  340. value, err := e.evalExpr(expr.Expr, row)
  341. if err != nil {
  342. return nil, err
  343. }
  344. index, err := e.evalExpr(expr.Index, row)
  345. if err != nil {
  346. return nil, err
  347. }
  348. return subscriptArrayValue(value, index)
  349. }
  350. // subscriptArrayValue applies a 1-based subscript to an evaluated array value.
  351. func subscriptArrayValue(value, index interface{}) (interface{}, error) {
  352. if value == nil || index == nil {
  353. return nil, nil
  354. }
  355. elements, err := parseArrayLiteral(toString(value))
  356. if err != nil {
  357. return nil, fmt.Errorf("array subscript requires an array value: %w", err)
  358. }
  359. position := toInt64(index)
  360. if position < 1 || position > int64(len(elements)) {
  361. return nil, nil
  362. }
  363. element := elements[position-1]
  364. if element == nil {
  365. return nil, nil
  366. }
  367. return *element, nil
  368. }
  369. // evalAnyAllExpr evaluates `left op ANY(array)` / `left op ALL(array)` with SQL
  370. // three-valued logic. ANY is true when any comparison is true, false when none
  371. // is true and no comparison is NULL, and NULL otherwise; ALL is its dual.
  372. func (e *Executor) evalAnyAllExpr(expr *parser.AnyAllExpr, row storage.Row) (interface{}, error) {
  373. left, err := e.evalExpr(expr.Left, row)
  374. if err != nil {
  375. return nil, err
  376. }
  377. arrayValue, err := e.evalExpr(expr.Array, row)
  378. if err != nil {
  379. return nil, err
  380. }
  381. if arrayValue == nil {
  382. return nil, nil
  383. }
  384. elements, err := parseArrayLiteral(toString(arrayValue))
  385. if err != nil {
  386. return nil, fmt.Errorf("ANY/ALL requires an array operand: %w", err)
  387. }
  388. return evalQuantifiedComparison(expr.Op, expr.All, left, elements)
  389. }
  390. // evalQuantifiedComparison applies a comparison to each array element. The
  391. // empty array is decided before the left operand: ANY over no elements is
  392. // false and ALL over no elements is true, even when the left operand is NULL.
  393. func evalQuantifiedComparison(op lexer.TokenType, all bool, left interface{}, elements []*string) (interface{}, error) {
  394. if len(elements) == 0 {
  395. return all, nil
  396. }
  397. if left == nil {
  398. return nil, nil
  399. }
  400. sawNull := false
  401. for _, element := range elements {
  402. if element == nil {
  403. sawNull = true
  404. continue
  405. }
  406. matched, err := compareArrayElement(op, left, *element)
  407. if err != nil {
  408. return nil, err
  409. }
  410. if all && !matched {
  411. return false, nil
  412. }
  413. if !all && matched {
  414. return true, nil
  415. }
  416. }
  417. if sawNull {
  418. return nil, nil
  419. }
  420. return all, nil
  421. }
  422. // compareArrayElement applies a comparison operator between a scalar left value
  423. // and one array element's text. Booleans are decoded so `true = ANY('{t,f}')`
  424. // compares as booleans; every other type compares through the engine's numeric
  425. // or textual comparison rules.
  426. func compareArrayElement(op lexer.TokenType, left interface{}, element string) (bool, error) {
  427. var right interface{} = element
  428. if _, isBool := left.(bool); isBool {
  429. value, ok := parseArrayBoolean(element)
  430. if !ok {
  431. return false, pgerror.InvalidText("invalid input syntax for type boolean: %q", element)
  432. }
  433. right = value
  434. }
  435. cmp := compare(left, right)
  436. switch op {
  437. case lexer.TokenEq:
  438. return cmp == 0, nil
  439. case lexer.TokenNeq:
  440. return cmp != 0, nil
  441. case lexer.TokenLt:
  442. return cmp < 0, nil
  443. case lexer.TokenLte:
  444. return cmp <= 0, nil
  445. case lexer.TokenGt:
  446. return cmp > 0, nil
  447. case lexer.TokenGte:
  448. return cmp >= 0, nil
  449. default:
  450. return false, fmt.Errorf("unsupported ANY/ALL comparison operator: %v", op)
  451. }
  452. }
  453. // evalArrayLength implements array_length(array, dimension). Only dimension 1 is
  454. // meaningful for a one-dimensional array; any other dimension yields NULL, as
  455. // PostgreSQL does for an out-of-range dimension of a non-nested array.
  456. func evalArrayLength(args []interface{}) (interface{}, error) {
  457. if len(args) != 2 {
  458. return nil, fmt.Errorf("array_length requires two arguments")
  459. }
  460. if args[0] == nil || args[1] == nil {
  461. return nil, nil
  462. }
  463. if toInt64(args[1]) != 1 {
  464. return nil, nil
  465. }
  466. elements, err := parseArrayLiteral(toString(args[0]))
  467. if err != nil {
  468. return nil, fmt.Errorf("array_length requires an array value: %w", err)
  469. }
  470. if len(elements) == 0 {
  471. // PostgreSQL reports NULL for the length of an empty array.
  472. return nil, nil
  473. }
  474. return int64(len(elements)), nil
  475. }
  476. // evalArrayCast converts a value to an array type. The input must already be
  477. // array text (a literal, a parameter, or another array value); every element is
  478. // validated and re-encoded canonically for the target element type.
  479. func evalArrayCast(target parser.DataType, value interface{}) (interface{}, error) {
  480. if value == nil {
  481. return nil, nil
  482. }
  483. text, ok := value.(string)
  484. if !ok {
  485. return nil, pgerror.InvalidText("cannot cast %T to %s", value, target.SQLTypeName())
  486. }
  487. return canonicalArrayText(text, target.SQLTypeName())
  488. }
  489. // canonicalArrayDefault canonicalizes a constant DEFAULT for an array column so
  490. // the catalog and any ALTER TABLE backfill store the same bytes an INSERT would.
  491. func canonicalArrayDefault(typeName string, value interface{}, exprText string) (interface{}, string, error) {
  492. if _, isArray := arrayElementType(typeName); !isArray || exprText != "" || value == nil {
  493. return value, exprText, nil
  494. }
  495. text, ok := value.(string)
  496. if !ok {
  497. return nil, "", pgerror.InvalidText("DEFAULT for %s must be array text, got %T", typeName, value)
  498. }
  499. canonical, err := canonicalArrayText(text, typeName)
  500. if err != nil {
  501. return nil, "", err
  502. }
  503. return canonical, "", nil
  504. }
  505. // normalizeArrayRow canonicalizes every array-typed column value in a row before
  506. // it is written. A string value is parsed as array text; an already-canonical
  507. // value is re-encoded identically, so the encoding is stable across reloads and
  508. // every write path shares one representation.
  509. func (e *Executor) normalizeArrayRow(schema *storage.Schema, row storage.Row) error {
  510. if schema == nil {
  511. return nil
  512. }
  513. for _, column := range schema.Columns {
  514. if _, isArray := arrayElementType(column.Type); !isArray {
  515. continue
  516. }
  517. value, ok := lookupRowValue(row, column.Name)
  518. if !ok || value == nil {
  519. continue
  520. }
  521. text, isText := value.(string)
  522. if !isText {
  523. return pgerror.InvalidText("column %q is %s and accepts array text, got %T", column.Name, column.Type, value)
  524. }
  525. canonical, err := canonicalArrayText(text, column.Type)
  526. if err != nil {
  527. return fmt.Errorf("column %q: %w", column.Name, err)
  528. }
  529. row[column.Name] = canonical
  530. }
  531. return nil
  532. }