| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004 |
- package executor
- import (
- "fmt"
- "math/big"
- "strconv"
- "strings"
- "github.com/danfragoso/pizzasql-next/pkg/lexer"
- "github.com/danfragoso/pizzasql-next/pkg/parser"
- "github.com/danfragoso/pizzasql-next/pkg/pgerror"
- "github.com/danfragoso/pizzasql-next/pkg/storage"
- )
- // Array values are carried as their canonical PostgreSQL array-literal text.
- // Storing the text keeps the row codec unchanged (a string is a string), makes
- // NULL and the empty array distinguishable (nil vs "{}"), and gives reloads a
- // stable encoding: every write path canonicalizes before storage, so equal
- // arrays always encode to identical bytes.
- //
- // The canonical form uses unquoted elements when the element text is unambiguous
- // and double-quoted (backslash-escaped) elements otherwise. Element text is
- // canonicalized per declared element type: integers to decimal, reals to Go's
- // shortest round-trip form, booleans to t/f.
- // arrayElementKind classifies an array element type for canonicalization.
- type arrayElementKind int
- const (
- arrayElementText arrayElementKind = iota
- arrayElementInteger
- arrayElementReal
- arrayElementNumeric
- arrayElementBoolean
- )
- // arrayElementType reports the base element type of an array type name such as
- // "INTEGER[]". It reports false for non-array type names.
- func arrayElementType(typeName string) (string, bool) {
- upper := strings.ToUpper(strings.TrimSpace(typeName))
- if !strings.HasSuffix(upper, "[]") {
- return "", false
- }
- return strings.TrimSpace(strings.TrimSuffix(upper, "[]")), true
- }
- // arrayElementKindOf reports the canonicalization kind for an array type name.
- func arrayElementKindOf(typeName string) (arrayElementKind, error) {
- element, ok := arrayElementType(typeName)
- if !ok {
- return arrayElementText, fmt.Errorf("%s is not an array type", typeName)
- }
- return arrayElementKindForName(element)
- }
- // arrayElementKindForName classifies a scalar element type name.
- func arrayElementKindForName(name string) (arrayElementKind, error) {
- switch strings.ToUpper(strings.TrimSpace(name)) {
- case "TEXT", "VARCHAR", "CHAR", "CHARACTER", "CLOB", "NCHAR", "NVARCHAR", "UUID":
- return arrayElementText, nil
- case "INTEGER", "INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT":
- return arrayElementInteger, nil
- case "REAL", "FLOAT", "DOUBLE":
- return arrayElementReal, nil
- case "NUMERIC", "DECIMAL":
- // NUMERIC elements keep their exact text: the engine's float64 carrier
- // would lose digits above 2^53 and trailing zeros.
- return arrayElementNumeric, nil
- case "BOOLEAN", "BOOL":
- return arrayElementBoolean, nil
- default:
- 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))
- }
- }
- // parseArrayLiteral splits PostgreSQL array-literal text into element strings.
- // A nil element is SQL NULL. Nested braces (multidimensional arrays) are
- // rejected, as is malformed quoting.
- func parseArrayLiteral(text string) ([]*string, error) {
- s := strings.TrimSpace(text)
- if len(s) < 2 || s[0] != '{' || s[len(s)-1] != '}' {
- return nil, invalidArrayInput(text, "array text must be enclosed in {}")
- }
- inner := s[1 : len(s)-1]
- if strings.TrimSpace(inner) == "" {
- return nil, nil
- }
- var elements []*string
- i := 0
- for {
- for i < len(inner) && isArraySpace(inner[i]) {
- i++
- }
- if i >= len(inner) {
- return nil, invalidArrayInput(text, "missing } after comma")
- }
- var value *string
- if inner[i] == '"' {
- i++
- var b strings.Builder
- closed := false
- for i < len(inner) {
- c := inner[i]
- if c == '\\' {
- if i+1 >= len(inner) {
- return nil, invalidArrayInput(text, "trailing backslash in quoted element")
- }
- b.WriteByte(inner[i+1])
- i += 2
- continue
- }
- if c == '"' {
- i++
- closed = true
- break
- }
- b.WriteByte(c)
- i++
- }
- if !closed {
- return nil, invalidArrayInput(text, "unterminated quoted element")
- }
- quoted := b.String()
- value = "ed
- for i < len(inner) && isArraySpace(inner[i]) {
- i++
- }
- } else {
- if inner[i] == '{' {
- return nil, pgerror.Unsupported("multidimensional arrays are not supported; array text must be one-dimensional")
- }
- start := i
- for i < len(inner) && inner[i] != ',' {
- if inner[i] == '{' || inner[i] == '}' || inner[i] == '"' {
- return nil, invalidArrayInput(text, "malformed array element")
- }
- i++
- }
- raw := strings.TrimSpace(inner[start:i])
- if raw == "" {
- return nil, invalidArrayInput(text, "empty array element")
- }
- if !strings.EqualFold(raw, "NULL") {
- value = &raw
- }
- }
- elements = append(elements, value)
- if i >= len(inner) {
- return elements, nil
- }
- if inner[i] != ',' {
- return nil, invalidArrayInput(text, "expected , between elements")
- }
- i++
- if i >= len(inner) {
- return nil, invalidArrayInput(text, "trailing comma")
- }
- }
- }
- func isArraySpace(c byte) bool {
- return c == ' ' || c == '\t' || c == '\n' || c == '\r'
- }
- // invalidArrayInput reports malformed array text as a client input error.
- func invalidArrayInput(text, reason string) error {
- return pgerror.InvalidText("invalid array input %q: %s", text, reason)
- }
- // formatArrayLiteral renders elements in canonical array text.
- func formatArrayLiteral(elements []*string) string {
- var b strings.Builder
- b.WriteByte('{')
- for i, element := range elements {
- if i > 0 {
- b.WriteByte(',')
- }
- if element == nil {
- b.WriteString("NULL")
- continue
- }
- b.WriteString(formatArrayElementText(*element))
- }
- b.WriteByte('}')
- return b.String()
- }
- // formatArrayElementText quotes an element when leaving it bare would change its
- // meaning: empty text, the NULL keyword, or characters that delimit elements.
- func formatArrayElementText(text string) string {
- needsQuote := text == "" || strings.EqualFold(text, "NULL")
- for i := 0; i < len(text) && !needsQuote; i++ {
- switch text[i] {
- case '{', '}', ',', '"', '\\', ' ', '\t', '\n', '\r':
- needsQuote = true
- }
- }
- if !needsQuote {
- return text
- }
- var b strings.Builder
- b.WriteByte('"')
- for i := 0; i < len(text); i++ {
- if text[i] == '"' || text[i] == '\\' {
- b.WriteByte('\\')
- }
- b.WriteByte(text[i])
- }
- b.WriteByte('"')
- return b.String()
- }
- // CanonicalizeArrayText validates PostgreSQL array text for an array type and
- // returns its canonical encoding. The protocol layer uses it to reject a
- // malformed bound array parameter at Bind time instead of waiting for a row to
- // evaluate the predicate.
- func CanonicalizeArrayText(text, typeName string) (string, error) {
- return canonicalArrayText(text, typeName)
- }
- // ParseArrayText splits stored array text into its element strings, with nil for
- // a SQL NULL element. The wire layer reuses it to encode the same text in the
- // PostgreSQL binary array format, so both formats are produced from one
- // canonical representation.
- func ParseArrayText(text string) ([]*string, error) {
- return parseArrayLiteral(text)
- }
- // canonicalArrayText parses array text and re-encodes it for the given array
- // type, converting each element to its canonical spelling.
- func canonicalArrayText(text, typeName string) (string, error) {
- kind, err := arrayElementKindOf(typeName)
- if err != nil {
- return "", err
- }
- elements, err := parseArrayLiteral(text)
- if err != nil {
- return "", err
- }
- canonical := make([]*string, len(elements))
- for i, element := range elements {
- value, elementErr := canonicalArrayElement(element, kind, typeName)
- if elementErr != nil {
- return "", elementErr
- }
- canonical[i] = value
- }
- return formatArrayLiteral(canonical), nil
- }
- // canonicalArrayElement converts one element to its canonical text. Element
- // types the engine does not carry exactly (integer widths, floating point) are
- // parsed and re-formatted so equivalent inputs encode identically.
- func canonicalArrayElement(element *string, kind arrayElementKind, typeName string) (*string, error) {
- if element == nil {
- return nil, nil
- }
- raw := *element
- switch kind {
- case arrayElementInteger:
- n, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
- if err != nil {
- return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
- }
- canonical := strconv.FormatInt(n, 10)
- return &canonical, nil
- case arrayElementReal:
- f, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
- if err != nil {
- return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
- }
- canonical := strconv.FormatFloat(f, 'g', -1, 64)
- return &canonical, nil
- case arrayElementNumeric:
- canonical, ok := normalizeNumericArrayElement(raw)
- if !ok {
- return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
- }
- return &canonical, nil
- case arrayElementBoolean:
- value, ok := parseArrayBoolean(raw)
- if !ok {
- return nil, pgerror.InvalidText("invalid input syntax for type %s: %q", typeName, raw)
- }
- canonical := "f"
- if value {
- canonical = "t"
- }
- return &canonical, nil
- default:
- canonical := raw
- return &canonical, nil
- }
- }
- // parseArrayBoolean accepts the boolean spellings PostgreSQL accepts in a text
- // array element and reports the value.
- func parseArrayBoolean(raw string) (bool, bool) {
- switch strings.ToLower(strings.TrimSpace(raw)) {
- case "t", "true", "1", "y", "yes", "on":
- return true, true
- case "f", "false", "0", "n", "no", "off":
- return false, true
- default:
- return false, false
- }
- }
- // arrayElementValue converts canonical element text to the Go value the engine
- // uses for that element type. Text elements stay strings.
- func arrayElementValue(element *string, elementType string) (interface{}, error) {
- if element == nil {
- return nil, nil
- }
- kind, err := arrayElementKindForName(elementType)
- if err != nil {
- return nil, err
- }
- raw := *element
- switch kind {
- case arrayElementInteger:
- n, err := strconv.ParseInt(raw, 10, 64)
- if err != nil {
- return nil, pgerror.InvalidText("invalid input syntax for type integer: %q", raw)
- }
- return n, nil
- case arrayElementReal:
- f, err := strconv.ParseFloat(raw, 64)
- if err != nil {
- return nil, pgerror.InvalidText("invalid input syntax for type double precision: %q", raw)
- }
- return f, nil
- case arrayElementNumeric:
- canonical, ok := normalizeNumericArrayElement(raw)
- if !ok {
- return nil, pgerror.InvalidText("invalid input syntax for type numeric: %q", raw)
- }
- // Return the text so a value above 2^53 keeps every digit.
- return canonical, nil
- case arrayElementBoolean:
- value, ok := parseArrayBoolean(raw)
- if !ok {
- return nil, pgerror.InvalidText("invalid input syntax for type boolean: %q", raw)
- }
- return value, nil
- default:
- return raw, nil
- }
- }
- // normalizeNumericArrayElement validates SQL NUMERIC syntax and returns the
- // canonical spelling: surrounding whitespace trimmed and a leading + removed.
- // Go's ParseFloat is not used because it also accepts hex floats, NaN, and Inf,
- // which NUMERIC has no representation for here.
- func normalizeNumericArrayElement(raw string) (string, bool) {
- text := strings.TrimSpace(raw)
- if text == "" {
- return "", false
- }
- if text[0] == '+' {
- text = text[1:]
- }
- if text == "" {
- return "", false
- }
- i := 0
- if text[i] == '-' {
- i++
- }
- digits := 0
- for i < len(text) && text[i] >= '0' && text[i] <= '9' {
- i++
- digits++
- }
- if i < len(text) && text[i] == '.' {
- i++
- for i < len(text) && text[i] >= '0' && text[i] <= '9' {
- i++
- digits++
- }
- }
- if digits == 0 {
- return "", false
- }
- if i < len(text) && (text[i] == 'e' || text[i] == 'E') {
- i++
- if i < len(text) && (text[i] == '+' || text[i] == '-') {
- i++
- }
- exponentDigits := 0
- for i < len(text) && text[i] >= '0' && text[i] <= '9' {
- i++
- exponentDigits++
- }
- if exponentDigits == 0 {
- return "", false
- }
- }
- if i != len(text) {
- return "", false
- }
- return text, true
- }
- // numericValueRat converts an evaluated value to an exact rational. Strings and
- // floats use their decimal spelling, so a column value "1.10" and the float
- // literal 1.1 both compare as 11/10 rather than through binary rounding.
- func numericValueRat(value interface{}) (*big.Rat, bool) {
- var text string
- switch v := value.(type) {
- case string:
- normalized, ok := normalizeNumericArrayElement(v)
- if !ok {
- return nil, false
- }
- text = normalized
- case int64:
- return new(big.Rat).SetInt64(v), true
- case int:
- return new(big.Rat).SetInt64(int64(v)), true
- case float64:
- text = strconv.FormatFloat(v, 'g', -1, 64)
- default:
- return nil, false
- }
- rat, ok := new(big.Rat).SetString(text)
- return rat, ok
- }
- // compareNumericValues compares two values exactly when both are numeric.
- func compareNumericValues(left, right interface{}) (int, bool) {
- leftRat, ok := numericValueRat(left)
- if !ok {
- return 0, false
- }
- rightRat, ok := numericValueRat(right)
- if !ok {
- return 0, false
- }
- return leftRat.Cmp(rightRat), true
- }
- // numericArrayTextEqual reports whether two canonical NUMERIC array texts are
- // equal element-wise, with NULL elements equal to each other. Other array types
- // compare by their canonical bytes.
- func numericArrayTextEqual(left, right, typeName string) (bool, error) {
- kind, err := arrayElementKindOf(typeName)
- if err != nil {
- return false, err
- }
- if kind != arrayElementNumeric {
- return left == right, nil
- }
- leftElements, err := parseArrayLiteral(left)
- if err != nil {
- return false, err
- }
- rightElements, err := parseArrayLiteral(right)
- if err != nil {
- return false, err
- }
- if len(leftElements) != len(rightElements) {
- return false, nil
- }
- for i := range leftElements {
- leftElement, rightElement := leftElements[i], rightElements[i]
- if leftElement == nil || rightElement == nil {
- if leftElement != nil || rightElement != nil {
- return false, nil
- }
- continue
- }
- cmp, ok := compareNumericValues(*leftElement, *rightElement)
- if !ok {
- return false, pgerror.InvalidText("invalid input syntax for type numeric: %q", *leftElement)
- }
- if cmp != 0 {
- return false, nil
- }
- }
- return true, nil
- }
- // arrayValueElement renders one evaluated expression value as a canonical array
- // element. Array nesting and non-scalar values are rejected.
- func arrayValueElement(value interface{}) (*string, error) {
- switch v := value.(type) {
- case nil:
- return nil, nil
- case bool:
- text := "f"
- if v {
- text = "t"
- }
- return &text, nil
- case int64:
- text := strconv.FormatInt(v, 10)
- return &text, nil
- case int:
- text := strconv.FormatInt(int64(v), 10)
- return &text, nil
- case float64:
- text := strconv.FormatFloat(v, 'g', -1, 64)
- return &text, nil
- case string:
- return &v, nil
- case []byte:
- return nil, pgerror.Unsupported("arrays of BLOB are not supported")
- default:
- return nil, pgerror.Unsupported("ARRAY[...] element type %T is not supported", value)
- }
- }
- // evalArrayExpr evaluates the ARRAY[...] constructor into canonical array text.
- func (e *Executor) evalArrayExpr(expr *parser.ArrayExpr, row storage.Row) (interface{}, error) {
- elements := make([]*string, len(expr.Elements))
- for i, element := range expr.Elements {
- value, err := e.evalExpr(element, row)
- if err != nil {
- return nil, err
- }
- text, err := arrayValueElement(value)
- if err != nil {
- return nil, fmt.Errorf("ARRAY element %d: %w", i+1, err)
- }
- elements[i] = text
- }
- return formatArrayLiteral(elements), nil
- }
- // arrayExpressionIsConstant reports whether evaluating an array expression once
- // per statement is safe: it references no columns and no volatile function.
- func arrayExpressionIsConstant(expr parser.Expr) bool {
- if expr == nil || len(collectColumnRefs(expr)) != 0 {
- return false
- }
- return ValidateDeterministicExpr(expr) == nil
- }
- // arrayOperand evaluates an array-valued expression through the per-statement
- // value cache. A constant operand is evaluated once for the whole scan instead
- // of once per row; a column-dependent operand is re-evaluated every time.
- func (e *Executor) arrayOperand(expr parser.Expr, evaluate func() (interface{}, error)) (interface{}, error) {
- if e.arrayValueCache != nil && expr != nil {
- if value, ok := e.arrayValueCache[expr]; ok {
- return value, nil
- }
- }
- value, err := evaluate()
- if err != nil {
- return nil, err
- }
- if e.arrayValueCache != nil && expr != nil && arrayExpressionIsConstant(expr) {
- e.arrayValueCache[expr] = value
- }
- return value, nil
- }
- // arrayParseEntry memoizes the parsed elements of one array expression for the
- // current statement. The evaluated text is compared before a hit is used, so a
- // value that changes between rows or between executions is re-parsed.
- type arrayParseEntry struct {
- text string
- elements []*string
- err error
- }
- // parsedArray parses an evaluated array value for one expression. The
- // per-statement cache keyed by AST node keeps `= ANY($1)` to one parse for the
- // whole scan instead of one parse per row.
- func (e *Executor) parsedArray(expr parser.Expr, value interface{}) ([]*string, error) {
- text := toString(value)
- if e.arrayCache != nil && expr != nil {
- if entry, ok := e.arrayCache[expr]; ok && entry.text == text {
- return entry.elements, entry.err
- }
- }
- elements, err := parseArrayLiteral(text)
- if e.arrayCache != nil && expr != nil {
- e.arrayCache[expr] = &arrayParseEntry{text: text, elements: elements, err: err}
- }
- return elements, err
- }
- // isNumericExprType reports whether an expression's declared type is NUMERIC or
- // DECIMAL, which compares exactly instead of through float64. CAST(... AS
- // NUMERIC) is included even though its result metadata stays unchanged: the
- // cast leaves the value's carrier alone, but comparisons still mean decimal.
- func (e *Executor) isNumericExprType(expr parser.Expr) bool {
- switch x := expr.(type) {
- case nil:
- return false
- case *parser.ParenExpr:
- return e.isNumericExprType(x.Expr)
- case *parser.CastExpr:
- switch strings.ToUpper(strings.TrimSpace(x.Type.Name)) {
- case "NUMERIC", "DECIMAL":
- return true
- }
- return false
- }
- switch strings.ToUpper(strings.TrimSpace(inferExprType(expr, e.resolveColumnType))) {
- case "NUMERIC", "DECIMAL":
- return true
- default:
- return false
- }
- }
- // comparisonOperatorResult applies a comparison operator to a three-way result.
- func comparisonOperatorResult(op lexer.TokenType, cmp int) (bool, error) {
- switch op {
- case lexer.TokenEq:
- return cmp == 0, nil
- case lexer.TokenNeq:
- return cmp != 0, nil
- case lexer.TokenLt:
- return cmp < 0, nil
- case lexer.TokenLte:
- return cmp <= 0, nil
- case lexer.TokenGt:
- return cmp > 0, nil
- case lexer.TokenGte:
- return cmp >= 0, nil
- default:
- return false, fmt.Errorf("unsupported comparison operator: %v", op)
- }
- }
- // arrayEqualityType reports the array type used for a whole-array equality
- // comparison. The left side wins when both are arrays.
- func (e *Executor) arrayEqualityType(leftExpr, rightExpr parser.Expr) (string, bool) {
- if typeName, ok := e.arrayTypeOf(leftExpr); ok {
- return typeName, true
- }
- return e.arrayTypeOf(rightExpr)
- }
- // compareExprValues applies a comparison operator to two evaluated values. A
- // NUMERIC-typed operand compares exactly (including element-wise numeric array
- // equality); everything else keeps the engine's existing comparison rules.
- func (e *Executor) compareExprValues(op lexer.TokenType, leftExpr, rightExpr parser.Expr, left, right interface{}) (bool, error) {
- if e.isJSONBExpr(leftExpr) || e.isJSONBExpr(rightExpr) {
- // A jsonb comparison must not silently reinterpret a SQL number or
- // boolean as a JSON value; PostgreSQL has no such operator.
- if leftExpr != nil && e.isJSONBExpr(leftExpr) && !jsonbComparisonValue(left) {
- return false, pgerror.DatatypeMismatch("jsonb comparison requires jsonb or text operands")
- }
- if rightExpr != nil && e.isJSONBExpr(rightExpr) && !jsonbComparisonValue(right) {
- return false, pgerror.DatatypeMismatch("jsonb comparison requires jsonb or text operands")
- }
- // jsonb equality and ordering are defined on the canonical form, so a
- // document with a different key order or number spelling compares equal.
- leftText, err := CanonicalizeJSONB(toString(left))
- if err != nil {
- return false, err
- }
- rightText, err := CanonicalizeJSONB(toString(right))
- if err != nil {
- return false, err
- }
- // Compare exact canonical bytes; the general compare() helper would round
- // numbers through float64 and disagree with =, DISTINCT, and @>.
- return comparisonOperatorResult(op, strings.Compare(leftText, rightText))
- }
- if op == lexer.TokenEq || op == lexer.TokenNeq {
- if typeName, ok := e.arrayEqualityType(leftExpr, rightExpr); ok {
- equal, err := numericArrayTextEqual(toString(left), toString(right), typeName)
- if err != nil {
- return false, err
- }
- if op == lexer.TokenNeq {
- return !equal, nil
- }
- return equal, nil
- }
- }
- if e.isNumericExprType(leftExpr) || e.isNumericExprType(rightExpr) {
- if cmp, ok := compareNumericValues(left, right); ok {
- return comparisonOperatorResult(op, cmp)
- }
- }
- return comparisonOperatorResult(op, compare(left, right))
- }
- // arrayElementTypeOf reports the element type of an expression whose value is an
- // array, resolved against the declared column types of the query currently
- // executing. An unknown type (a derived table, a bare literal) reports false so
- // the caller keeps the element text.
- func (e *Executor) arrayElementTypeOf(expr parser.Expr) (string, bool) {
- if expr == nil {
- return "", false
- }
- return arrayElementType(inferExprType(expr, e.resolveColumnType))
- }
- // arrayIndexValue validates an array subscript or array_length dimension.
- // PostgreSQL's signatures take integers; floats, booleans, and text are a
- // datatype mismatch, not an implicit truncation.
- func arrayIndexValue(value interface{}, what string) (int64, error) {
- switch v := value.(type) {
- case int64:
- return v, nil
- case int:
- return int64(v), nil
- case int32:
- return int64(v), nil
- case int16:
- return int64(v), nil
- case int8:
- return int64(v), nil
- default:
- return 0, pgerror.DatatypeMismatch("%s must be an integer, got %T", what, value)
- }
- }
- // evalSubscriptExpr evaluates arr[index] with PostgreSQL's 1-based indexing. An
- // out-of-range subscript yields NULL, and a NULL array, index, or element yields
- // NULL. The element is decoded to the array's declared element type so a
- // boolean subscript is a Go bool rather than the canonical t/f text.
- func (e *Executor) evalSubscriptExpr(expr *parser.SubscriptExpr, row storage.Row) (interface{}, error) {
- value, err := e.arrayOperand(expr.Expr, func() (interface{}, error) {
- return e.evalExpr(expr.Expr, row)
- })
- if err != nil {
- return nil, err
- }
- index, err := e.evalExpr(expr.Index, row)
- if err != nil {
- return nil, err
- }
- return e.subscriptArrayValue(expr.Expr, value, index)
- }
- // subscriptArrayValue applies a 1-based subscript to an evaluated array value.
- func (e *Executor) subscriptArrayValue(arrayExpr parser.Expr, value, index interface{}) (interface{}, error) {
- if value == nil || index == nil {
- return nil, nil
- }
- position, err := arrayIndexValue(index, "array subscript")
- if err != nil {
- return nil, err
- }
- elements, err := e.parsedArray(arrayExpr, value)
- if err != nil {
- return nil, fmt.Errorf("array subscript requires an array value: %w", err)
- }
- if position < 1 || position > int64(len(elements)) {
- return nil, nil
- }
- element := elements[position-1]
- if element == nil {
- return nil, nil
- }
- elementType, ok := e.arrayElementTypeOf(arrayExpr)
- if !ok {
- return *element, nil
- }
- return arrayElementValue(element, elementType)
- }
- // evalAnyAllExpr evaluates `left op ANY(array)` / `left op ALL(array)` with SQL
- // three-valued logic. ANY is true when any comparison is true, false when none
- // is true and no comparison is NULL, and NULL otherwise; ALL is its dual.
- func (e *Executor) evalAnyAllExpr(expr *parser.AnyAllExpr, row storage.Row) (interface{}, error) {
- left, err := e.evalExpr(expr.Left, row)
- if err != nil {
- return nil, err
- }
- arrayValue, err := e.arrayOperand(expr.Array, func() (interface{}, error) {
- return e.evalExpr(expr.Array, row)
- })
- if err != nil {
- return nil, err
- }
- if arrayValue == nil {
- return nil, nil
- }
- elements, err := e.parsedArray(expr.Array, arrayValue)
- if err != nil {
- return nil, fmt.Errorf("ANY/ALL requires an array operand: %w", err)
- }
- elementType, _ := e.arrayElementTypeOf(expr.Array)
- return evalQuantifiedComparison(expr.Op, expr.All, left, elements, elementType)
- }
- // evalQuantifiedComparison applies a comparison to each array element. The
- // empty array is decided before the left operand: ANY over no elements is
- // false and ALL over no elements is true, even when the left operand is NULL.
- func evalQuantifiedComparison(op lexer.TokenType, all bool, left interface{}, elements []*string, elementType string) (interface{}, error) {
- if len(elements) == 0 {
- return all, nil
- }
- if left == nil {
- return nil, nil
- }
- sawNull := false
- for _, element := range elements {
- if element == nil {
- sawNull = true
- continue
- }
- matched, err := compareArrayElement(op, left, *element, elementType)
- if err != nil {
- return nil, err
- }
- if all && !matched {
- return false, nil
- }
- if !all && matched {
- return true, nil
- }
- }
- if sawNull {
- return nil, nil
- }
- return all, nil
- }
- // compareArrayElement applies a comparison operator between a scalar left value
- // and one array element's text. Booleans are decoded so `true = ANY('{t,f}')`
- // compares as booleans; every other type compares through the engine's numeric
- // or textual comparison rules.
- func compareArrayElement(op lexer.TokenType, left interface{}, element, elementType string) (bool, error) {
- if kind, err := arrayElementKindForName(elementType); err == nil && kind == arrayElementNumeric {
- cmp, ok := compareNumericValues(left, element)
- if !ok {
- return false, pgerror.InvalidText("invalid input syntax for type numeric: %q", element)
- }
- return comparisonOperatorResult(op, cmp)
- }
- var right interface{} = element
- if _, isBool := left.(bool); isBool {
- value, ok := parseArrayBoolean(element)
- if !ok {
- return false, pgerror.InvalidText("invalid input syntax for type boolean: %q", element)
- }
- right = value
- }
- return comparisonOperatorResult(op, compare(left, right))
- }
- // evalArrayLength implements array_length(array, dimension). Only dimension 1 is
- // meaningful for a one-dimensional array; any other dimension yields NULL, as
- // PostgreSQL does for an out-of-range dimension of a non-nested array.
- func (e *Executor) evalArrayLength(arrayExpr parser.Expr, args []interface{}) (interface{}, error) {
- if len(args) != 2 {
- return nil, fmt.Errorf("array_length requires two arguments")
- }
- if args[0] == nil || args[1] == nil {
- return nil, nil
- }
- dimension, err := arrayIndexValue(args[1], "array dimension")
- if err != nil {
- return nil, err
- }
- if dimension != 1 {
- return nil, nil
- }
- elements, err := e.parsedArray(arrayExpr, args[0])
- if err != nil {
- return nil, fmt.Errorf("array_length requires an array value: %w", err)
- }
- if len(elements) == 0 {
- // PostgreSQL reports NULL for the length of an empty array.
- return nil, nil
- }
- return int64(len(elements)), nil
- }
- // arrayTypeOf reports the declared array type of an expression, if any.
- func (e *Executor) arrayTypeOf(expr parser.Expr) (string, bool) {
- if expr == nil {
- return "", false
- }
- typeName := inferExprType(expr, e.resolveColumnType)
- if _, ok := arrayElementType(typeName); !ok {
- return "", false
- }
- return typeName, true
- }
- // arrayCanonicalEntry memoizes one canonicalized array comparison operand for
- // the current statement, validated by the source text and target type.
- type arrayCanonicalEntry struct {
- text string
- typeName string
- canonical string
- err error
- }
- // canonicalArrayValue canonicalizes a value against the array type of expr. It
- // is a no-op for non-array expressions, NULL, and non-string values. The result
- // is memoized per AST node and source text, so a constant literal on one side of
- // a scan predicate is canonicalized once per statement.
- func (e *Executor) canonicalArrayValue(arrayExpr parser.Expr, value interface{}) (interface{}, error) {
- typeName, ok := e.arrayTypeOf(arrayExpr)
- if !ok || value == nil {
- return value, nil
- }
- text, isText := value.(string)
- if !isText {
- return value, nil
- }
- if e.arrayCanonicalCache != nil && arrayExpr != nil {
- if entry, cached := e.arrayCanonicalCache[arrayExpr]; cached && entry.text == text && entry.typeName == typeName {
- if entry.err != nil {
- return nil, entry.err
- }
- return entry.canonical, nil
- }
- }
- canonical, err := canonicalArrayText(text, typeName)
- if e.arrayCanonicalCache != nil && arrayExpr != nil {
- e.arrayCanonicalCache[arrayExpr] = &arrayCanonicalEntry{text: text, typeName: typeName, canonical: canonical, err: err}
- }
- if err != nil {
- return nil, err
- }
- return canonical, nil
- }
- // canonicalizeArrayComparison canonicalizes the string operand of a comparison
- // when the other operand is array-typed, so a text literal such as '{a, b}' or
- // '{"a","b"}' compares equal to the canonical stored '{a,b}'.
- func (e *Executor) canonicalizeArrayComparison(leftExpr, rightExpr parser.Expr, left, right interface{}) (interface{}, interface{}, error) {
- if _, ok := e.arrayTypeOf(leftExpr); ok {
- canonical, err := e.canonicalArrayValue(leftExpr, right)
- if err != nil {
- return nil, nil, err
- }
- right = canonical
- }
- if _, ok := e.arrayTypeOf(rightExpr); ok {
- canonical, err := e.canonicalArrayValue(rightExpr, left)
- if err != nil {
- return nil, nil, err
- }
- left = canonical
- }
- return left, right, nil
- }
- // evalArrayCast converts a value to an array type. The input must already be
- // array text (a literal, a parameter, or another array value); every element is
- // validated and re-encoded canonically for the target element type.
- func evalArrayCast(target parser.DataType, value interface{}) (interface{}, error) {
- if value == nil {
- return nil, nil
- }
- text, ok := value.(string)
- if !ok {
- return nil, pgerror.InvalidText("cannot cast %T to %s", value, target.SQLTypeName())
- }
- return canonicalArrayText(text, target.SQLTypeName())
- }
- // canonicalArrayDefault canonicalizes a constant DEFAULT for an array column so
- // the catalog and any ALTER TABLE backfill store the same bytes an INSERT would.
- func canonicalArrayDefault(typeName string, value interface{}, exprText string) (interface{}, string, error) {
- if _, isArray := arrayElementType(typeName); !isArray || exprText != "" || value == nil {
- return value, exprText, nil
- }
- text, ok := value.(string)
- if !ok {
- return nil, "", pgerror.InvalidText("DEFAULT for %s must be array text, got %T", typeName, value)
- }
- canonical, err := canonicalArrayText(text, typeName)
- if err != nil {
- return nil, "", err
- }
- return canonical, "", nil
- }
- // normalizeArrayRow canonicalizes every array-typed column value in a row before
- // it is written. A string value is parsed as array text; an already-canonical
- // value is re-encoded identically, so the encoding is stable across reloads and
- // every write path shares one representation.
- func (e *Executor) normalizeArrayRow(schema *storage.Schema, row storage.Row) error {
- if schema == nil {
- return nil
- }
- for _, column := range schema.Columns {
- if _, isArray := arrayElementType(column.Type); !isArray {
- continue
- }
- value, ok := lookupRowValue(row, column.Name)
- if !ok || value == nil {
- continue
- }
- text, isText := value.(string)
- if !isText {
- return pgerror.InvalidText("column %q is %s and accepts array text, got %T", column.Name, column.Type, value)
- }
- canonical, err := canonicalArrayText(text, column.Type)
- if err != nil {
- return fmt.Errorf("column %q: %w", column.Name, err)
- }
- row[column.Name] = canonical
- }
- return nil
- }
|