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
179 lines
5.1 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|