github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sqlbase/check.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sqlbase 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/parser" 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/cockroach/pkg/sql/types" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // CheckHelper validates check constraints on rows; it is used only by the 25 // legacy foreign-key path. 26 // 27 // CheckHelper analyzes and evaluates each check constraint as a standalone 28 // expression. This is used by the heuristic planner, and is the 29 // backwards-compatible code path. 30 // 31 // Callers should call NewEvalCheckHelper to initialize a new instance of 32 // CheckHelper. For each row, they call LoadEvalRow one or more times to set row 33 // values for evaluation, and then call CheckEval to trigger evaluation. 34 type CheckHelper struct { 35 Exprs []tree.TypedExpr 36 cols []ColumnDescriptor 37 sourceInfo *DataSourceInfo 38 ivarHelper *tree.IndexedVarHelper 39 curSourceRow tree.Datums 40 } 41 42 // AnalyzeExprFunction is the function type used by the CheckHelper during 43 // initialization to analyze an expression. See sql/analyze_expr.go for details 44 // about the function. 45 type AnalyzeExprFunction func( 46 ctx context.Context, 47 raw tree.Expr, 48 source *DataSourceInfo, 49 iVarHelper tree.IndexedVarHelper, 50 expectedType *types.T, 51 requireType bool, 52 typingContext string, 53 ) (tree.TypedExpr, error) 54 55 // NewEvalCheckHelper constructs a new instance of the CheckHelper. 56 func NewEvalCheckHelper( 57 ctx context.Context, analyzeExpr AnalyzeExprFunction, tableDesc *ImmutableTableDescriptor, 58 ) (*CheckHelper, error) { 59 if len(tableDesc.ActiveChecks()) == 0 { 60 return nil, nil 61 } 62 63 c := &CheckHelper{} 64 c.cols = tableDesc.AllNonDropColumns() 65 c.sourceInfo = NewSourceInfoForSingleTable( 66 tree.MakeUnqualifiedTableName(tree.Name(tableDesc.Name)), 67 ResultColumnsFromColDescs(tableDesc.GetID(), c.cols), 68 ) 69 70 c.Exprs = make([]tree.TypedExpr, len(tableDesc.ActiveChecks())) 71 exprStrings := make([]string, len(tableDesc.ActiveChecks())) 72 for i, check := range tableDesc.ActiveChecks() { 73 exprStrings[i] = check.Expr 74 } 75 exprs, err := parser.ParseExprs(exprStrings) 76 if err != nil { 77 return nil, err 78 } 79 80 ivarHelper := tree.MakeIndexedVarHelper(c, len(c.cols)) 81 for i, raw := range exprs { 82 typedExpr, err := analyzeExpr( 83 ctx, 84 raw, 85 c.sourceInfo, 86 ivarHelper, 87 types.Bool, 88 false, /* requireType */ 89 "", /* typingContext */ 90 ) 91 if err != nil { 92 return nil, err 93 } 94 c.Exprs[i] = typedExpr 95 } 96 c.ivarHelper = &ivarHelper 97 c.curSourceRow = make(tree.Datums, len(c.cols)) 98 return c, nil 99 } 100 101 // LoadEvalRow sets values in the IndexedVars used by the CHECK exprs. 102 // Any value not passed is set to NULL, unless `merge` is true, in which 103 // case it is left unchanged (allowing updating a subset of a row's values). 104 func (c *CheckHelper) LoadEvalRow(colIdx map[ColumnID]int, row tree.Datums, merge bool) error { 105 if len(c.Exprs) == 0 { 106 return nil 107 } 108 // Populate IndexedVars. 109 for _, ivar := range c.ivarHelper.GetIndexedVars() { 110 if !ivar.Used { 111 continue 112 } 113 ri, has := colIdx[c.cols[ivar.Idx].ID] 114 if has { 115 if row[ri] != tree.DNull { 116 expected, provided := ivar.ResolvedType(), row[ri].ResolvedType() 117 if !expected.Equivalent(provided) { 118 return errors.Errorf("%s value does not match CHECK expr type %s", provided, expected) 119 } 120 } 121 c.curSourceRow[ivar.Idx] = row[ri] 122 } else if !merge { 123 c.curSourceRow[ivar.Idx] = tree.DNull 124 } 125 } 126 return nil 127 } 128 129 // IndexedVarEval implements the tree.IndexedVarContainer interface. 130 func (c *CheckHelper) IndexedVarEval(idx int, ctx *tree.EvalContext) (tree.Datum, error) { 131 return c.curSourceRow[idx].Eval(ctx) 132 } 133 134 // IndexedVarResolvedType implements the tree.IndexedVarContainer interface. 135 func (c *CheckHelper) IndexedVarResolvedType(idx int) *types.T { 136 return c.sourceInfo.SourceColumns[idx].Typ 137 } 138 139 // IndexedVarNodeFormatter implements the parser.IndexedVarContainer interface. 140 func (c *CheckHelper) IndexedVarNodeFormatter(idx int) tree.NodeFormatter { 141 return c.sourceInfo.NodeFormatter(idx) 142 } 143 144 // CheckEval evaluates each check constraint expression using values from the 145 // current row that was previously set via a call to LoadEvalRow. 146 func (c *CheckHelper) CheckEval(ctx *tree.EvalContext) error { 147 ctx.PushIVarContainer(c) 148 defer func() { ctx.PopIVarContainer() }() 149 for _, expr := range c.Exprs { 150 if d, err := expr.Eval(ctx); err != nil { 151 return err 152 } else if res, err := tree.GetBool(d); err != nil { 153 return err 154 } else if !res && d != tree.DNull { 155 // Failed to satisfy CHECK constraint. 156 return pgerror.Newf(pgcode.CheckViolation, 157 "failed to satisfy CHECK constraint (%s)", expr) 158 } 159 } 160 return nil 161 }