| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package parser
- import (
- "fmt"
- "strings"
- "github.com/danfragoso/pizzasql-next/pkg/lexer"
- )
- // FormatExpr renders an expression as canonical SQL text. It is used to persist
- // expression indexes and generated-column definitions in the durable schema, so
- // the output only needs to be stable and re-parseable, not byte-identical to the
- // original input. A nil expression formats as an empty string.
- func FormatExpr(expr Expr) string {
- if expr == nil {
- return ""
- }
- switch e := expr.(type) {
- case *LiteralExpr:
- switch e.Type {
- case lexer.TokenString:
- return "'" + strings.ReplaceAll(e.Value, "'", "''") + "'"
- case lexer.TokenBlob:
- return "X'" + fmt.Sprintf("%X", []byte(e.Value)) + "'"
- case lexer.TokenNULL:
- return "NULL"
- case lexer.TokenTRUE:
- return "TRUE"
- case lexer.TokenFALSE:
- return "FALSE"
- default:
- return e.Value
- }
- case *ColumnRef:
- if e.Table != "" {
- return quoteFormatIdent(e.Table) + "." + quoteFormatIdent(e.Column)
- }
- return quoteFormatIdent(e.Column)
- case *BinaryExpr:
- return fmt.Sprintf("(%s %s %s)", FormatExpr(e.Left), operatorString(e.Op), FormatExpr(e.Right))
- case *UnaryExpr:
- if e.Op == lexer.TokenNOT {
- return fmt.Sprintf("(NOT %s)", FormatExpr(e.Operand))
- }
- return fmt.Sprintf("(%s%s)", operatorString(e.Op), FormatExpr(e.Operand))
- case *ParenExpr:
- return fmt.Sprintf("(%s)", FormatExpr(e.Expr))
- case *FunctionCall:
- if e.Star {
- return strings.ToLower(e.Name) + "(*)"
- }
- args := make([]string, len(e.Args))
- for i, a := range e.Args {
- args[i] = FormatExpr(a)
- }
- prefix := ""
- if e.Distinct {
- prefix = "DISTINCT "
- }
- return strings.ToLower(e.Name) + "(" + prefix + strings.Join(args, ", ") + ")"
- case *CastExpr:
- return fmt.Sprintf("CAST(%s AS %s)", FormatExpr(e.Expr), e.Type.Name)
- case *CaseExpr:
- var b strings.Builder
- b.WriteString("CASE")
- if e.Operand != nil {
- b.WriteString(" ")
- b.WriteString(FormatExpr(e.Operand))
- }
- for _, w := range e.Whens {
- b.WriteString(" WHEN ")
- b.WriteString(FormatExpr(w.Condition))
- b.WriteString(" THEN ")
- b.WriteString(FormatExpr(w.Result))
- }
- if e.Else != nil {
- b.WriteString(" ELSE ")
- b.WriteString(FormatExpr(e.Else))
- }
- b.WriteString(" END")
- return b.String()
- case *InExpr:
- not := ""
- if e.Not {
- not = "NOT "
- }
- if e.Subquery != nil {
- return fmt.Sprintf("(%s %sIN (SELECT ...))", FormatExpr(e.Left), not)
- }
- vals := make([]string, len(e.Values))
- for i, v := range e.Values {
- vals[i] = FormatExpr(v)
- }
- return fmt.Sprintf("(%s %sIN (%s))", FormatExpr(e.Left), not, strings.Join(vals, ", "))
- case *BetweenExpr:
- not := ""
- if e.Not {
- not = "NOT "
- }
- return fmt.Sprintf("(%s %sBETWEEN %s AND %s)", FormatExpr(e.Left), not, FormatExpr(e.Low), FormatExpr(e.High))
- case *LikeExpr:
- not := ""
- if e.Not {
- not = "NOT "
- }
- out := fmt.Sprintf("(%s %sLIKE %s)", FormatExpr(e.Left), not, FormatExpr(e.Pattern))
- if e.Escape != nil {
- out = fmt.Sprintf("(%s %sLIKE %s ESCAPE %s)", FormatExpr(e.Left), not, FormatExpr(e.Pattern), FormatExpr(e.Escape))
- }
- return out
- case *IsNullExpr:
- if e.Not {
- return fmt.Sprintf("(%s IS NOT NULL)", FormatExpr(e.Left))
- }
- return fmt.Sprintf("(%s IS NULL)", FormatExpr(e.Left))
- case *IsDistinctExpr:
- op := "IS DISTINCT FROM"
- if e.Not {
- op = "IS NOT DISTINCT FROM"
- }
- return fmt.Sprintf("(%s %s %s)", FormatExpr(e.Left), op, FormatExpr(e.Right))
- case *SubqueryExpr:
- return "(SELECT ...)"
- default:
- return ""
- }
- }
- // operatorString renders an operator token back to SQL.
- func operatorString(op lexer.TokenType) string {
- switch op {
- case lexer.TokenPlus:
- return "+"
- case lexer.TokenMinus:
- return "-"
- case lexer.TokenStar:
- return "*"
- case lexer.TokenSlash:
- return "/"
- case lexer.TokenPercent:
- return "%"
- case lexer.TokenConcat:
- return "||"
- case lexer.TokenEq:
- return "="
- case lexer.TokenNeq:
- return "<>"
- case lexer.TokenLt:
- return "<"
- case lexer.TokenLte:
- return "<="
- case lexer.TokenGt:
- return ">"
- case lexer.TokenGte:
- return ">="
- case lexer.TokenAND:
- return "AND"
- case lexer.TokenOR:
- return "OR"
- case lexer.TokenBitAnd:
- return "&"
- case lexer.TokenBitOr:
- return "|"
- case lexer.TokenBitNot:
- return "~"
- case lexer.TokenShiftLeft:
- return "<<"
- case lexer.TokenShiftRight:
- return ">>"
- default:
- return op.String()
- }
- }
- // quoteFormatIdent quotes an identifier when it is not a bare word.
- func quoteFormatIdent(name string) string {
- if name == "" {
- return name
- }
- bare := true
- for i := 0; i < len(name); i++ {
- c := name[i]
- if !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9')) {
- bare = false
- break
- }
- }
- if bare {
- return name
- }
- return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
- }
|