Files
upstream-sqlc/internal/sql/ast/a_expr.go
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

179 lines
5.1 KiB
Go

package ast
import (
"strings"
"github.com/sqlc-dev/sqlc/internal/sql/format"
)
type A_Expr struct {
Tag NodeTag[A_Expr] `json:"tag"`
Kind A_Expr_Kind `json:"kind"`
// Name is the operator, as a list of String nodes: PostgreSQL's
// operator space is open (user-defined and schema-qualified operators),
// so the shared tree keeps pg_query's shape rather than an enum. Each
// engine's converter writes the operator as its parser saw it — for an
// engine that accepts more than one spelling of the same operator
// (SQLite's != for <>, == for =), that is the author's spelling, which
// is how the printer preserves it.
Name *List `json:"name,omitempty"`
Lexpr Node `json:"lexpr,omitempty"`
Rexpr Node `json:"rexpr,omitempty"`
Location int `json:"location"`
}
func (n *A_Expr) Pos() int {
return n.Location
}
// isNamedParam reports whether this A_Expr represents a named parameter,
// returning its sigil and name. Engines encode a named parameter as a
// prefix pseudo-operator carrying the sigil the author wrote (@name for
// most engines; SQLite also spells :name and $name). The spellings mean
// different things to sqlc, so the printer emits the one it was given.
func (n *A_Expr) isNamedParam() (sigil, name string, ok bool) {
if n.Name == nil || len(n.Name.Items) != 1 {
return "", "", false
}
s, sok := n.Name.Items[0].(*String)
if !sok || (s.Str != "@" && s.Str != ":" && s.Str != "$") {
return "", "", false
}
if set(n.Lexpr) || !set(n.Rexpr) {
return "", "", false
}
if nameStr, sok := n.Rexpr.(*String); sok {
return s.Str, nameStr.Str, true
}
// Before the compiler rewrites named parameters, @name parses as the @
// operator applied to a bare column reference.
if col, sok := n.Rexpr.(*ColumnRef); sok && col.Fields != nil && len(col.Fields.Items) == 1 {
if f, sok := col.Fields.Items[0].(*String); sok {
return s.Str, f.Str, true
}
}
return "", "", false
}
// gluePrefix reports that this prefix expression must print with no space
// between the operator and its operand. `@name::type` is a sqlc named
// parameter with a cast: the cast binds tighter than the operator, so the
// parse is @ applied to a TypeCast and isNamedParam does not see it — but
// `@ name::type` would break the parameter apart for sqlc's scanner. Glue
// the sigils onto any operand that starts with an identifier, which also
// keeps a genuine prefix operator like pg's absolute-value @ meaning the
// same thing.
func (n *A_Expr) gluePrefix() bool {
if set(n.Lexpr) || n.Name == nil || len(n.Name.Items) != 1 {
return false
}
s, ok := n.Name.Items[0].(*String)
if !ok || (s.Str != "@" && s.Str != ":" && s.Str != "$") {
return false
}
operand := n.Rexpr
if tc, ok := operand.(*TypeCast); ok {
operand = tc.Arg
}
switch operand.(type) {
case *ColumnRef, *FuncCall:
return true
}
return false
}
// negated returns true when the expression's operator carries the negated
// spelling of a pattern-match operator (e.g. "!~~" for NOT LIKE).
func (n *A_Expr) negated() bool {
if n.Name == nil || len(n.Name.Items) != 1 {
return false
}
s, ok := n.Name.Items[0].(*String)
return ok && strings.HasPrefix(s.Str, "!")
}
func (n *A_Expr) Format(buf *TrackedBuffer, d format.Dialect) {
if n == nil {
return
}
// Check for named parameter first (works regardless of Kind)
if sigil, name, ok := n.isNamedParam(); ok {
buf.WriteString(sigil)
buf.WriteString(name)
return
}
switch n.Kind {
case A_Expr_Kind_IN:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" IN (")
buf.astFormat(n.Rexpr, d)
buf.WriteString(")")
case A_Expr_Kind_LIKE:
buf.astFormat(n.Lexpr, d)
if n.negated() {
buf.WriteString(" NOT LIKE ")
} else {
buf.WriteString(" LIKE ")
}
buf.astFormat(n.Rexpr, d)
case A_Expr_Kind_ILIKE:
buf.astFormat(n.Lexpr, d)
if n.negated() {
buf.WriteString(" NOT ILIKE ")
} else {
buf.WriteString(" ILIKE ")
}
buf.astFormat(n.Rexpr, d)
case A_Expr_Kind_SIMILAR:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" SIMILAR TO ")
buf.astFormat(n.Rexpr, d)
case A_Expr_Kind_BETWEEN:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" BETWEEN ")
if l, ok := n.Rexpr.(*List); ok && len(l.Items) == 2 {
buf.astFormat(l.Items[0], d)
buf.WriteString(" AND ")
buf.astFormat(l.Items[1], d)
}
case A_Expr_Kind_NOT_BETWEEN:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" NOT BETWEEN ")
if l, ok := n.Rexpr.(*List); ok && len(l.Items) == 2 {
buf.astFormat(l.Items[0], d)
buf.WriteString(" AND ")
buf.astFormat(l.Items[1], d)
}
case A_Expr_Kind_DISTINCT:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" IS DISTINCT FROM ")
buf.astFormat(n.Rexpr, d)
case A_Expr_Kind_NOT_DISTINCT:
buf.astFormat(n.Lexpr, d)
buf.WriteString(" IS NOT DISTINCT FROM ")
buf.astFormat(n.Rexpr, d)
case A_Expr_Kind_NULLIF:
buf.WriteString("NULLIF(")
buf.astFormat(n.Lexpr, d)
buf.WriteString(", ")
buf.astFormat(n.Rexpr, d)
buf.WriteString(")")
default:
// Standard operator (including A_Expr_Kind_OP)
if set(n.Lexpr) {
buf.astFormat(n.Lexpr, d)
buf.WriteString(" ")
}
buf.astFormat(n.Name, d)
if set(n.Rexpr) {
if !n.gluePrefix() {
buf.WriteString(" ")
}
buf.astFormat(n.Rexpr, d)
}
}
}