ddl_defaults.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package executor
  2. import (
  3. "fmt"
  4. "strings"
  5. "sync"
  6. "time"
  7. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  8. "github.com/danfragoso/pizzasql-next/pkg/parser"
  9. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  10. "github.com/danfragoso/pizzasql-next/pkg/storage"
  11. )
  12. // defaultColumnValue resolves a DEFAULT declaration into the representation the
  13. // schema catalog persists. A constant is evaluated once and stored as its value.
  14. // The whitelist of clock-reading defaults is stored as SQL text instead, so it
  15. // is evaluated on every write rather than frozen at CREATE/ALTER time.
  16. //
  17. // Every other non-constant expression is rejected: the catalog would have to
  18. // store its SQL text and a write would have to evaluate it, and silently
  19. // freezing the DDL-time value is worse than refusing the declaration.
  20. func defaultColumnValue(e *Executor, column string, expr parser.Expr) (interface{}, string, error) {
  21. if isConstantExpr(expr) {
  22. value, err := e.evalExpr(expr, nil)
  23. if err != nil {
  24. return nil, "", fmt.Errorf("evaluate DEFAULT for column %q: %w", column, err)
  25. }
  26. return value, "", nil
  27. }
  28. if isWhitelistedDefaultExpr(expr) {
  29. return nil, parser.FormatExpr(expr), nil
  30. }
  31. return nil, "", pgerror.Unsupported(
  32. "DEFAULT for column %q is not a constant expression: %s; PizzaSQL evaluates a non-constant default at write time only for CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, datetime('now'), date('now'), and time('now'). Omit the default and supply the value on INSERT",
  33. column, parser.FormatExpr(expr),
  34. )
  35. }
  36. // isConstantExpr reports whether expr is a literal, a unary sign applied to a
  37. // literal, a parenthesized constant, or a cast of a constant. Everything else
  38. // (column references including CURRENT_TIMESTAMP, function calls, subqueries,
  39. // arithmetic) is non-constant.
  40. func isConstantExpr(expr parser.Expr) bool {
  41. switch ex := expr.(type) {
  42. case *parser.LiteralExpr:
  43. return true
  44. case *parser.UnaryExpr:
  45. if ex.Op != lexer.TokenMinus && ex.Op != lexer.TokenPlus {
  46. return false
  47. }
  48. return isConstantExpr(ex.Operand)
  49. case *parser.ParenExpr:
  50. return isConstantExpr(ex.Expr)
  51. case *parser.CastExpr:
  52. return isConstantExpr(ex.Expr)
  53. default:
  54. return false
  55. }
  56. }
  57. // isWhitelistedDefaultExpr reports whether expr is one of the clock-reading
  58. // defaults PizzaSQL evaluates per write. The list is deliberately small: these
  59. // are the forms that SQLite applications declare, and each maps to a value the
  60. // engine already produces for the equivalent expression.
  61. func isWhitelistedDefaultExpr(expr parser.Expr) bool {
  62. switch ex := expr.(type) {
  63. case *parser.ColumnRef:
  64. if ex.Table != "" {
  65. return false
  66. }
  67. switch strings.ToLower(ex.Column) {
  68. case "current_timestamp", "current_date", "current_time":
  69. return true
  70. }
  71. case *parser.FunctionCall:
  72. if len(ex.Args) != 1 {
  73. return false
  74. }
  75. switch strings.ToLower(ex.Name) {
  76. case "datetime", "date", "time":
  77. default:
  78. return false
  79. }
  80. lit, ok := ex.Args[0].(*parser.LiteralExpr)
  81. return ok && lit.Type == lexer.TokenString && strings.EqualFold(lit.Value, "now")
  82. }
  83. return false
  84. }
  85. // defaultExprCache holds parsed default expressions. The stored form is SQL
  86. // text validated at DDL time, so parsing it again per statement uses the same
  87. // parser path and keeps the catalog independent of the AST.
  88. var defaultExprCache sync.Map // map[string]parser.Expr
  89. func parseDefaultExpr(text string) (parser.Expr, error) {
  90. if cached, ok := defaultExprCache.Load(text); ok {
  91. return cached.(parser.Expr), nil
  92. }
  93. expr, err := parser.ParseExpr(text)
  94. if err != nil {
  95. return nil, fmt.Errorf("invalid stored DEFAULT expression %q: %w", text, err)
  96. }
  97. defaultExprCache.Store(text, expr)
  98. return expr, nil
  99. }
  100. // EvalDefaultExpression evaluates a persisted non-constant DEFAULT expression
  101. // at the current time. It is the storage layer's evaluator for columns added
  102. // with ALTER TABLE ADD COLUMN and for row images that predate such a column; an
  103. // INSERT or UPDATE evaluates the expression once per statement itself.
  104. func EvalDefaultExpression(text string) (interface{}, error) {
  105. return evalDefaultExpressionAt(text, time.Now().UTC())
  106. }
  107. // evalDefaultExpressionAt evaluates a persisted default with an explicit clock.
  108. // The value shapes match what the same expression returns in a query, so a
  109. // defaulted timestamp column compares equal to CURRENT_TIMESTAMP:
  110. // CURRENT_TIMESTAMP and datetime('now') yield "2006-01-02 15:04:05" UTC,
  111. // CURRENT_DATE and date('now') yield "2006-01-02", and CURRENT_TIME and
  112. // time('now') yield "15:04:05". That space-separated UTC text is what the
  113. // engine already stores and what the timestamptz OID path emits, so clients
  114. // that decode timestamptz accept it.
  115. func evalDefaultExpressionAt(text string, now time.Time) (interface{}, error) {
  116. expr, err := parseDefaultExpr(text)
  117. if err != nil {
  118. return nil, err
  119. }
  120. if !isWhitelistedDefaultExpr(expr) {
  121. return nil, pgerror.Unsupported(
  122. "stored DEFAULT expression %q is not supported; PizzaSQL only evaluates CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, datetime('now'), date('now'), and time('now') at write time",
  123. text,
  124. )
  125. }
  126. switch ex := expr.(type) {
  127. case *parser.ColumnRef:
  128. value, _ := sqliteCurrentTimeValueAt(ex.Column, now)
  129. return value, nil
  130. case *parser.FunctionCall:
  131. keyword := map[string]string{
  132. "datetime": "current_timestamp",
  133. "date": "current_date",
  134. "time": "current_time",
  135. }[strings.ToLower(ex.Name)]
  136. value, _ := sqliteCurrentTimeValueAt(keyword, now)
  137. return value, nil
  138. }
  139. return nil, pgerror.Unsupported("stored DEFAULT expression %q is not supported", text)
  140. }
  141. // statementDefaults evaluates every expression default in the schema once for
  142. // the statement. A multi-row INSERT therefore stamps all its rows with one
  143. // value, while a later statement reads the clock again. Constant defaults stay
  144. // in the catalog and are applied by the storage layer.
  145. func (e *Executor) statementDefaults(schema *storage.Schema) (map[string]interface{}, error) {
  146. var defaults map[string]interface{}
  147. for _, column := range schema.Columns {
  148. if column.DefaultExpr == "" {
  149. continue
  150. }
  151. value, err := e.evalDefaultExpr(column.DefaultExpr)
  152. if err != nil {
  153. return nil, fmt.Errorf("evaluate DEFAULT for column %q: %w", column.Name, err)
  154. }
  155. if defaults == nil {
  156. defaults = make(map[string]interface{})
  157. }
  158. defaults[column.Name] = value
  159. }
  160. return defaults, nil
  161. }
  162. func (e *Executor) evalDefaultExpr(text string) (interface{}, error) {
  163. now := time.Now().UTC()
  164. if e.clock != nil {
  165. now = e.clock().UTC()
  166. }
  167. return evalDefaultExpressionAt(text, now)
  168. }
  169. // applyRowDefaults fills the expression-defaulted columns a statement omitted.
  170. // An explicit NULL is already present in the row, so it wins over the default.
  171. func applyRowDefaults(row storage.Row, defaults map[string]interface{}) {
  172. for name, value := range defaults {
  173. if _, present := lookupRowValue(row, name); present {
  174. continue
  175. }
  176. row[name] = value
  177. }
  178. }