ddl_defaults.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. case *parser.ArrayExpr:
  54. for _, element := range ex.Elements {
  55. if !isConstantExpr(element) {
  56. return false
  57. }
  58. }
  59. return true
  60. default:
  61. return false
  62. }
  63. }
  64. // isWhitelistedDefaultExpr reports whether expr is one of the clock-reading
  65. // defaults PizzaSQL evaluates per write. The list is deliberately small: these
  66. // are the forms that SQLite applications declare, and each maps to a value the
  67. // engine already produces for the equivalent expression.
  68. func isWhitelistedDefaultExpr(expr parser.Expr) bool {
  69. // SQLite applications commonly wrap a default in parentheses, for example
  70. // DEFAULT (datetime('now')). The wrapper does not change the expression.
  71. if paren, ok := expr.(*parser.ParenExpr); ok {
  72. return isWhitelistedDefaultExpr(paren.Expr)
  73. }
  74. switch ex := expr.(type) {
  75. case *parser.ColumnRef:
  76. if ex.Table != "" {
  77. return false
  78. }
  79. switch strings.ToLower(ex.Column) {
  80. case "current_timestamp", "current_date", "current_time":
  81. return true
  82. }
  83. case *parser.FunctionCall:
  84. if len(ex.Args) != 1 {
  85. return false
  86. }
  87. switch strings.ToLower(ex.Name) {
  88. case "datetime", "date", "time":
  89. default:
  90. return false
  91. }
  92. lit, ok := ex.Args[0].(*parser.LiteralExpr)
  93. return ok && lit.Type == lexer.TokenString && strings.EqualFold(lit.Value, "now")
  94. }
  95. return false
  96. }
  97. // defaultExprCache holds parsed default expressions. The stored form is SQL
  98. // text validated at DDL time, so parsing it again per statement uses the same
  99. // parser path and keeps the catalog independent of the AST.
  100. var defaultExprCache sync.Map // map[string]parser.Expr
  101. func parseDefaultExpr(text string) (parser.Expr, error) {
  102. if cached, ok := defaultExprCache.Load(text); ok {
  103. if expr, ok := cached.(parser.Expr); ok {
  104. return expr, nil
  105. }
  106. }
  107. expr, err := parser.ParseExpr(text)
  108. if err != nil {
  109. return nil, fmt.Errorf("invalid stored DEFAULT expression %q: %w", text, err)
  110. }
  111. defaultExprCache.Store(text, expr)
  112. return expr, nil
  113. }
  114. // evalDefaultExpressionAt evaluates a persisted default with an explicit clock.
  115. // The value shapes match what the same expression returns in a query, so a
  116. // defaulted timestamp column compares equal to CURRENT_TIMESTAMP:
  117. // CURRENT_TIMESTAMP and datetime('now') yield "2006-01-02 15:04:05" UTC,
  118. // CURRENT_DATE and date('now') yield "2006-01-02", and CURRENT_TIME and
  119. // time('now') yield "15:04:05". That space-separated UTC text is what the
  120. // engine already stores and what the timestamptz OID path emits, so clients
  121. // that decode timestamptz accept it.
  122. func evalDefaultExpressionAt(text string, now time.Time) (interface{}, error) {
  123. expr, err := parseDefaultExpr(text)
  124. if err != nil {
  125. return nil, err
  126. }
  127. for {
  128. paren, ok := expr.(*parser.ParenExpr)
  129. if !ok {
  130. break
  131. }
  132. expr = paren.Expr
  133. }
  134. if !isWhitelistedDefaultExpr(expr) {
  135. return nil, pgerror.Unsupported(
  136. "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",
  137. text,
  138. )
  139. }
  140. switch ex := expr.(type) {
  141. case *parser.ColumnRef:
  142. value, _ := sqliteCurrentTimeValueAt(ex.Column, now)
  143. return value, nil
  144. case *parser.FunctionCall:
  145. keyword := map[string]string{
  146. "datetime": "current_timestamp",
  147. "date": "current_date",
  148. "time": "current_time",
  149. }[strings.ToLower(ex.Name)]
  150. value, _ := sqliteCurrentTimeValueAt(keyword, now)
  151. return value, nil
  152. }
  153. return nil, pgerror.Unsupported("stored DEFAULT expression %q is not supported", text)
  154. }
  155. // statementDefaults resolves every default in the schema for the statement.
  156. // Expression defaults are evaluated once, so a multi-row INSERT stamps all its
  157. // rows with one value while a later statement reads the clock again. Constant
  158. // catalog defaults are included too: the conflict-matching and RETURNING paths
  159. // inspect the candidate row before storage fills missing columns, so a key
  160. // column that comes from a DEFAULT must already be present in it.
  161. func (e *Executor) statementDefaults(schema *storage.Schema) (map[string]interface{}, error) {
  162. // One instant per statement: two clock-default columns in the same row must
  163. // not disagree because the loop sampled the clock twice. The clock is read
  164. // lazily so a schema without expression defaults never samples it.
  165. var now time.Time
  166. sampledClock := false
  167. var defaults map[string]interface{}
  168. add := func(name string, value interface{}) {
  169. if defaults == nil {
  170. defaults = make(map[string]interface{})
  171. }
  172. defaults[name] = value
  173. }
  174. for _, column := range schema.Columns {
  175. if column.DefaultExpr == "" {
  176. if column.Default != nil {
  177. add(column.Name, column.Default)
  178. }
  179. continue
  180. }
  181. if !sampledClock {
  182. now = e.statementNow()
  183. sampledClock = true
  184. }
  185. value, err := evalDefaultExpressionAt(column.DefaultExpr, now)
  186. if err != nil {
  187. return nil, fmt.Errorf("evaluate DEFAULT for column %q: %w", column.Name, err)
  188. }
  189. add(column.Name, value)
  190. }
  191. return defaults, nil
  192. }
  193. // statementNow returns the instant one statement's expression defaults use.
  194. // The executor samples it once per INSERT/UPDATE; the storage layer samples it
  195. // once per ALTER TABLE statement. Tests inject e.clock to make it deterministic.
  196. func (e *Executor) statementNow() time.Time {
  197. if e.clock != nil {
  198. return e.clock().UTC()
  199. }
  200. return time.Now().UTC()
  201. }
  202. // applyRowDefaults fills the defaulted columns a statement omitted. An explicit
  203. // NULL is already present in the row, so it wins over the default.
  204. func applyRowDefaults(row storage.Row, defaults map[string]interface{}) {
  205. for name, value := range defaults {
  206. if _, present := lookupRowValue(row, name); present {
  207. continue
  208. }
  209. row[name] = value
  210. }
  211. }