snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
178
internal/sql/ast/a_expr.go
Normal file
178
internal/sql/ast/a_expr.go
Normal file
@@ -0,0 +1,178 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user