format.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. package parser
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  6. )
  7. // FormatExpr renders an expression as canonical SQL text. It is used to persist
  8. // expression indexes and generated-column definitions in the durable schema, so
  9. // the output only needs to be stable and re-parseable, not byte-identical to the
  10. // original input. A nil expression formats as an empty string.
  11. func FormatExpr(expr Expr) string {
  12. if expr == nil {
  13. return ""
  14. }
  15. switch e := expr.(type) {
  16. case *LiteralExpr:
  17. switch e.Type {
  18. case lexer.TokenString:
  19. return "'" + strings.ReplaceAll(e.Value, "'", "''") + "'"
  20. case lexer.TokenBlob:
  21. return "X'" + fmt.Sprintf("%X", []byte(e.Value)) + "'"
  22. case lexer.TokenNULL:
  23. return "NULL"
  24. case lexer.TokenTRUE:
  25. return "TRUE"
  26. case lexer.TokenFALSE:
  27. return "FALSE"
  28. default:
  29. return e.Value
  30. }
  31. case *ColumnRef:
  32. if e.Table != "" {
  33. return quoteFormatIdent(e.Table) + "." + quoteFormatIdent(e.Column)
  34. }
  35. return quoteFormatIdent(e.Column)
  36. case *BinaryExpr:
  37. return fmt.Sprintf("(%s %s %s)", FormatExpr(e.Left), operatorString(e.Op), FormatExpr(e.Right))
  38. case *UnaryExpr:
  39. if e.Op == lexer.TokenNOT {
  40. return fmt.Sprintf("(NOT %s)", FormatExpr(e.Operand))
  41. }
  42. return fmt.Sprintf("(%s%s)", operatorString(e.Op), FormatExpr(e.Operand))
  43. case *ParenExpr:
  44. return fmt.Sprintf("(%s)", FormatExpr(e.Expr))
  45. case *FunctionCall:
  46. if e.Star {
  47. return strings.ToLower(e.Name) + "(*)"
  48. }
  49. args := make([]string, len(e.Args))
  50. for i, a := range e.Args {
  51. args[i] = FormatExpr(a)
  52. }
  53. prefix := ""
  54. if e.Distinct {
  55. prefix = "DISTINCT "
  56. }
  57. return strings.ToLower(e.Name) + "(" + prefix + strings.Join(args, ", ") + ")"
  58. case *CastExpr:
  59. return fmt.Sprintf("CAST(%s AS %s)", FormatExpr(e.Expr), e.Type.SQLTypeName())
  60. case *ArrayExpr:
  61. elements := make([]string, len(e.Elements))
  62. for i, element := range e.Elements {
  63. elements[i] = FormatExpr(element)
  64. }
  65. return "ARRAY[" + strings.Join(elements, ", ") + "]"
  66. case *SubscriptExpr:
  67. return fmt.Sprintf("%s[%s]", FormatExpr(e.Expr), FormatExpr(e.Index))
  68. case *AnyAllExpr:
  69. quantifier := "ANY"
  70. if e.All {
  71. quantifier = "ALL"
  72. }
  73. return fmt.Sprintf("(%s %s %s(%s))", FormatExpr(e.Left), operatorString(e.Op), quantifier, FormatExpr(e.Array))
  74. case *CaseExpr:
  75. var b strings.Builder
  76. b.WriteString("CASE")
  77. if e.Operand != nil {
  78. b.WriteString(" ")
  79. b.WriteString(FormatExpr(e.Operand))
  80. }
  81. for _, w := range e.Whens {
  82. b.WriteString(" WHEN ")
  83. b.WriteString(FormatExpr(w.Condition))
  84. b.WriteString(" THEN ")
  85. b.WriteString(FormatExpr(w.Result))
  86. }
  87. if e.Else != nil {
  88. b.WriteString(" ELSE ")
  89. b.WriteString(FormatExpr(e.Else))
  90. }
  91. b.WriteString(" END")
  92. return b.String()
  93. case *InExpr:
  94. not := ""
  95. if e.Not {
  96. not = "NOT "
  97. }
  98. if e.Subquery != nil {
  99. return fmt.Sprintf("(%s %sIN (SELECT ...))", FormatExpr(e.Left), not)
  100. }
  101. vals := make([]string, len(e.Values))
  102. for i, v := range e.Values {
  103. vals[i] = FormatExpr(v)
  104. }
  105. return fmt.Sprintf("(%s %sIN (%s))", FormatExpr(e.Left), not, strings.Join(vals, ", "))
  106. case *BetweenExpr:
  107. not := ""
  108. if e.Not {
  109. not = "NOT "
  110. }
  111. return fmt.Sprintf("(%s %sBETWEEN %s AND %s)", FormatExpr(e.Left), not, FormatExpr(e.Low), FormatExpr(e.High))
  112. case *LikeExpr:
  113. not := ""
  114. if e.Not {
  115. not = "NOT "
  116. }
  117. out := fmt.Sprintf("(%s %sLIKE %s)", FormatExpr(e.Left), not, FormatExpr(e.Pattern))
  118. if e.Escape != nil {
  119. out = fmt.Sprintf("(%s %sLIKE %s ESCAPE %s)", FormatExpr(e.Left), not, FormatExpr(e.Pattern), FormatExpr(e.Escape))
  120. }
  121. return out
  122. case *IsNullExpr:
  123. if e.Not {
  124. return fmt.Sprintf("(%s IS NOT NULL)", FormatExpr(e.Left))
  125. }
  126. return fmt.Sprintf("(%s IS NULL)", FormatExpr(e.Left))
  127. case *IsDistinctExpr:
  128. op := "IS DISTINCT FROM"
  129. if e.Not {
  130. op = "IS NOT DISTINCT FROM"
  131. }
  132. return fmt.Sprintf("(%s %s %s)", FormatExpr(e.Left), op, FormatExpr(e.Right))
  133. case *SubqueryExpr:
  134. return "(SELECT ...)"
  135. default:
  136. return ""
  137. }
  138. }
  139. // operatorString renders an operator token back to SQL.
  140. func operatorString(op lexer.TokenType) string {
  141. switch op {
  142. case lexer.TokenPlus:
  143. return "+"
  144. case lexer.TokenMinus:
  145. return "-"
  146. case lexer.TokenStar:
  147. return "*"
  148. case lexer.TokenSlash:
  149. return "/"
  150. case lexer.TokenPercent:
  151. return "%"
  152. case lexer.TokenConcat:
  153. return "||"
  154. case lexer.TokenArrow:
  155. return "->"
  156. case lexer.TokenArrowText:
  157. return "->>"
  158. case lexer.TokenEq:
  159. return "="
  160. case lexer.TokenNeq:
  161. return "<>"
  162. case lexer.TokenLt:
  163. return "<"
  164. case lexer.TokenLte:
  165. return "<="
  166. case lexer.TokenGt:
  167. return ">"
  168. case lexer.TokenGte:
  169. return ">="
  170. case lexer.TokenAND:
  171. return "AND"
  172. case lexer.TokenOR:
  173. return "OR"
  174. case lexer.TokenBitAnd:
  175. return "&"
  176. case lexer.TokenBitOr:
  177. return "|"
  178. case lexer.TokenBitNot:
  179. return "~"
  180. case lexer.TokenShiftLeft:
  181. return "<<"
  182. case lexer.TokenShiftRight:
  183. return ">>"
  184. default:
  185. return op.String()
  186. }
  187. }
  188. // quoteFormatIdent quotes an identifier when it is not a bare word.
  189. func quoteFormatIdent(name string) string {
  190. if name == "" {
  191. return name
  192. }
  193. bare := true
  194. for i := 0; i < len(name); i++ {
  195. c := name[i]
  196. if !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9')) {
  197. bare = false
  198. break
  199. }
  200. }
  201. // Formatted text is persisted and re-parsed, so an identifier that lexes as
  202. // a SQL keyword must be quoted even when it is otherwise bare: a column
  203. // named "order" has to format as "order" to stay a column reference.
  204. if bare && lexer.LookupKeyword(strings.ToUpper(name)) == lexer.TokenIdent {
  205. return name
  206. }
  207. return `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
  208. }