checks.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package executor
  2. import (
  3. "fmt"
  4. "github.com/danfragoso/pizzasql-next/pkg/parser"
  5. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  6. "github.com/danfragoso/pizzasql-next/pkg/storage"
  7. )
  8. // EvalCheckExpression evaluates a persisted CHECK predicate against a row. It is
  9. // the storage layer's CHECK evaluator, stateless and concurrency-safe like
  10. // EvalStoredExpression.
  11. //
  12. // The result follows SQL three-valued logic: NULL and any other value that is
  13. // not definitely false passes the constraint. The truth test is the engine's
  14. // WHERE predicate test (toBool), so a CHECK and an equivalent WHERE filter agree
  15. // on the same row.
  16. func EvalCheckExpression(expression string, row storage.Row) (bool, error) {
  17. expr, err := parseStoredExpr(expression)
  18. if err != nil {
  19. return false, err
  20. }
  21. value, err := statelessEval.evalExpr(expr, row)
  22. if err != nil {
  23. return false, err
  24. }
  25. if value == nil {
  26. return true, nil
  27. }
  28. return toBool(value), nil
  29. }
  30. // checkConstraintFor turns a parsed CHECK predicate into its durable form. Only
  31. // deterministic, subquery-free predicates over unqualified columns of the table
  32. // are accepted: the expression is persisted as SQL text and re-evaluated on
  33. // every write, so an aggregate, window function, subquery, or clock/random
  34. // function could not be enforced consistently.
  35. func checkConstraintFor(name string, expr parser.Expr, schema *storage.Schema) (storage.CheckConstraint, error) {
  36. text := parser.FormatExpr(expr)
  37. if err := ValidateDeterministicExpr(expr); err != nil {
  38. return storage.CheckConstraint{}, pgerror.Unsupported(
  39. "CHECK expression %s is not allowed: %v; a CHECK predicate must be deterministic and free of subqueries, aggregates, and window functions",
  40. text, err,
  41. )
  42. }
  43. for _, ref := range collectTableColumnRefs(expr) {
  44. if ref.tbl != "" {
  45. return storage.CheckConstraint{}, pgerror.Unsupported(
  46. "CHECK expression %s uses the qualified column reference %s.%s; name the column without a table qualifier so the predicate stays bound to this table",
  47. text, ref.tbl, ref.col,
  48. )
  49. }
  50. }
  51. if err := ValidateIndexColumns(expr, schema); err != nil {
  52. return storage.CheckConstraint{}, pgerror.Unsupported(
  53. "CHECK expression %s is invalid: %v", text, err,
  54. )
  55. }
  56. // The predicate is persisted as this exact text and re-parsed by the
  57. // evaluator on every later write. Reject a DDL whose formatted text does not
  58. // round-trip instead of publishing a constraint that fails every write.
  59. if _, err := parseStoredExpr(text); err != nil {
  60. return storage.CheckConstraint{}, pgerror.Unsupported(
  61. "CHECK expression %s cannot be stored for later evaluation: %v; the predicate must round-trip through the engine's SQL text form",
  62. text, err,
  63. )
  64. }
  65. return storage.CheckConstraint{Name: name, Expression: text}, nil
  66. }
  67. // tableChecks appends the parsed CHECK constraints of a column definition or a
  68. // table-level constraint list. The schema passed in must already contain every
  69. // column the predicate may reference, so callers validate after the full column
  70. // list is built.
  71. func (e *Executor) tableChecks(schema *storage.Schema, columnConstraints []parser.ColumnConstraint, tableConstraints []parser.TableConstraint) ([]storage.CheckConstraint, error) {
  72. var checks []storage.CheckConstraint
  73. for _, constraint := range columnConstraints {
  74. if constraint.Type != parser.ConstraintCheck {
  75. continue
  76. }
  77. check, err := checkConstraintFor(constraint.Name, constraint.Check, schema)
  78. if err != nil {
  79. return nil, err
  80. }
  81. checks = append(checks, check)
  82. }
  83. for _, constraint := range tableConstraints {
  84. if constraint.Type != parser.ConstraintCheck {
  85. continue
  86. }
  87. check, err := checkConstraintFor(constraint.Name, constraint.Check, schema)
  88. if err != nil {
  89. return nil, err
  90. }
  91. checks = append(checks, check)
  92. }
  93. return checks, nil
  94. }
  95. // schemaWithColumn returns the table schema plus one not-yet-published column,
  96. // so a CHECK predicate declared on that column can be validated against the
  97. // table it will apply to.
  98. func schemaWithColumn(schema *storage.Schema, column storage.Column) *storage.Schema {
  99. extended := *schema
  100. extended.Columns = append(append([]storage.Column(nil), schema.Columns...), column)
  101. return &extended
  102. }
  103. // rejectCheckColumnChange refuses to rename or drop a column that a persisted
  104. // CHECK predicate references. Predicates are stored as SQL text, so the engine
  105. // cannot rewrite them; leaving the old identifier behind would make the
  106. // constraint evaluate to NULL and silently stop enforcing it.
  107. func (e *Executor) rejectCheckColumnChange(table, column, action string) error {
  108. schema, err := e.schema.GetSchema(table)
  109. if err != nil {
  110. return err
  111. }
  112. for _, check := range schema.Checks {
  113. references, err := storedExpressionReferencesColumn(check.Expression, column)
  114. if err != nil {
  115. return fmt.Errorf("CHECK constraint %s: %w", check.Label(), err)
  116. }
  117. if references {
  118. return pgerror.Unsupported(
  119. "cannot %s column %s: CHECK constraint %s depends on it; recreate the table without that constraint first",
  120. action, column, check.Label(),
  121. )
  122. }
  123. }
  124. return nil
  125. }