checks.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. return storage.CheckConstraint{Name: name, Expression: text}, nil
  57. }
  58. // tableChecks appends the parsed CHECK constraints of a column definition or a
  59. // table-level constraint list. The schema passed in must already contain every
  60. // column the predicate may reference, so callers validate after the full column
  61. // list is built.
  62. func (e *Executor) tableChecks(schema *storage.Schema, columnConstraints []parser.ColumnConstraint, tableConstraints []parser.TableConstraint) ([]storage.CheckConstraint, error) {
  63. var checks []storage.CheckConstraint
  64. for _, constraint := range columnConstraints {
  65. if constraint.Type != parser.ConstraintCheck {
  66. continue
  67. }
  68. check, err := checkConstraintFor(constraint.Name, constraint.Check, schema)
  69. if err != nil {
  70. return nil, err
  71. }
  72. checks = append(checks, check)
  73. }
  74. for _, constraint := range tableConstraints {
  75. if constraint.Type != parser.ConstraintCheck {
  76. continue
  77. }
  78. check, err := checkConstraintFor(constraint.Name, constraint.Check, schema)
  79. if err != nil {
  80. return nil, err
  81. }
  82. checks = append(checks, check)
  83. }
  84. return checks, nil
  85. }
  86. // schemaWithColumn returns the table schema plus one not-yet-published column,
  87. // so a CHECK predicate declared on that column can be validated against the
  88. // table it will apply to.
  89. func schemaWithColumn(schema *storage.Schema, column storage.Column) *storage.Schema {
  90. extended := *schema
  91. extended.Columns = append(append([]storage.Column(nil), schema.Columns...), column)
  92. return &extended
  93. }
  94. // rejectCheckColumnChange refuses to rename or drop a column that a persisted
  95. // CHECK predicate references. Predicates are stored as SQL text, so the engine
  96. // cannot rewrite them; leaving the old identifier behind would make the
  97. // constraint evaluate to NULL and silently stop enforcing it.
  98. func (e *Executor) rejectCheckColumnChange(table, column, action string) error {
  99. schema, err := e.schema.GetSchema(table)
  100. if err != nil {
  101. return err
  102. }
  103. for _, check := range schema.Checks {
  104. references, err := storedExpressionReferencesColumn(check.Expression, column)
  105. if err != nil {
  106. return fmt.Errorf("CHECK constraint %s: %w", check.Label(), err)
  107. }
  108. if references {
  109. return pgerror.Unsupported(
  110. "cannot %s column %s: CHECK constraint %s depends on it; recreate the table without that constraint first",
  111. action, column, check.Label(),
  112. )
  113. }
  114. }
  115. return nil
  116. }