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  }