github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/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 sql
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/keys"
    20  	"github.com/cockroachdb/cockroach/pkg/kv"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    26  	"github.com/cockroachdb/cockroach/pkg/util"
    27  	"github.com/cockroachdb/cockroach/pkg/util/log"
    28  	"github.com/cockroachdb/errors"
    29  )
    30  
    31  // validateCheckExpr verifies that the given CHECK expression returns true
    32  // for all the rows in the table.
    33  //
    34  // It operates entirely on the current goroutine and is thus able to
    35  // reuse an existing client.Txn safely.
    36  func validateCheckExpr(
    37  	ctx context.Context,
    38  	exprStr string,
    39  	tableDesc *sqlbase.TableDescriptor,
    40  	ie *InternalExecutor,
    41  	txn *kv.Txn,
    42  ) error {
    43  	expr, err := parser.ParseExpr(exprStr)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	// Construct AST and then convert to a string, to avoid problems with escaping the check expression
    48  	tblref := tree.TableRef{TableID: int64(tableDesc.ID), As: tree.AliasClause{Alias: "t"}}
    49  	sel := &tree.SelectClause{
    50  		Exprs: sqlbase.ColumnsSelectors(tableDesc.Columns),
    51  		From:  tree.From{Tables: []tree.TableExpr{&tblref}},
    52  		Where: &tree.Where{Type: tree.AstWhere, Expr: &tree.NotExpr{Expr: expr}},
    53  	}
    54  	lim := &tree.Limit{Count: tree.NewDInt(1)}
    55  	stmt := &tree.Select{Select: sel, Limit: lim}
    56  	queryStr := tree.AsStringWithFlags(stmt, tree.FmtParsable)
    57  	log.Infof(ctx, "Validating check constraint %q with query %q", expr.String(), queryStr)
    58  
    59  	rows, err := ie.QueryRow(ctx, "validate check constraint", txn, queryStr)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	if rows.Len() > 0 {
    64  		return pgerror.Newf(pgcode.CheckViolation,
    65  			"validation of CHECK %q failed on row: %s",
    66  			expr.String(), labeledRowValues(tableDesc.Columns, rows))
    67  	}
    68  	return nil
    69  }
    70  
    71  // matchFullUnacceptableKeyQuery generates and returns a query for rows that are
    72  // disallowed given the specified MATCH FULL composite FK reference, i.e., rows
    73  // in the referencing table where the key contains both null and non-null
    74  // values.
    75  //
    76  // For example, a FK constraint on columns (a_id, b_id) with an index c_id on
    77  // the table "child" would require the following query:
    78  //
    79  // SELECT s.a_id, s.b_id, s.pk1, s.pk2 FROM child@c_idx
    80  // WHERE
    81  //   (a_id IS NULL OR b_id IS NULL) AND (a_id IS NOT NULL OR b_id IS NOT NULL)
    82  // LIMIT 1;
    83  func matchFullUnacceptableKeyQuery(
    84  	srcTbl *sqlbase.TableDescriptor, fk *sqlbase.ForeignKeyConstraint, limitResults bool,
    85  ) (sql string, colNames []string, _ error) {
    86  	nCols := len(fk.OriginColumnIDs)
    87  	srcCols := make([]string, nCols)
    88  	srcNullExistsClause := make([]string, nCols)
    89  	srcNotNullExistsClause := make([]string, nCols)
    90  
    91  	returnedCols := srcCols
    92  	for i := 0; i < nCols; i++ {
    93  		col, err := srcTbl.FindColumnByID(fk.OriginColumnIDs[i])
    94  		if err != nil {
    95  			return "", nil, err
    96  		}
    97  		srcCols[i] = tree.NameString(col.Name)
    98  		srcNullExistsClause[i] = fmt.Sprintf("%s IS NULL", srcCols[i])
    99  		srcNotNullExistsClause[i] = fmt.Sprintf("%s IS NOT NULL", srcCols[i])
   100  	}
   101  
   102  	for _, id := range srcTbl.PrimaryIndex.ColumnIDs {
   103  		alreadyPresent := false
   104  		for _, otherID := range fk.OriginColumnIDs {
   105  			if id == otherID {
   106  				alreadyPresent = true
   107  				break
   108  			}
   109  		}
   110  		if !alreadyPresent {
   111  			col, err := srcTbl.FindActiveColumnByID(id)
   112  			if err != nil {
   113  				return "", nil, err
   114  			}
   115  			returnedCols = append(returnedCols, col.Name)
   116  		}
   117  	}
   118  
   119  	limit := ""
   120  	if limitResults {
   121  		limit = " LIMIT 1"
   122  	}
   123  	return fmt.Sprintf(
   124  		`SELECT %[1]s FROM [%[2]d AS tbl] WHERE (%[3]s) AND (%[4]s) %[5]s`,
   125  		strings.Join(returnedCols, ","),              // 1
   126  		srcTbl.ID,                                    // 2
   127  		strings.Join(srcNullExistsClause, " OR "),    // 3
   128  		strings.Join(srcNotNullExistsClause, " OR "), // 4
   129  		limit, // 5
   130  	), returnedCols, nil
   131  }
   132  
   133  // nonMatchingRowQuery generates and returns a query for rows that violate the
   134  // specified FK constraint, i.e., rows in the referencing table with no matching
   135  // key in the referenced table. Rows in the referencing table with any null
   136  // values in the key are excluded from matching (for both MATCH FULL and MATCH
   137  // SIMPLE).
   138  //
   139  // For example, a FK constraint on columns (a_id, b_id) with an index c_id on
   140  // the table "child", referencing columns (a, b) with an index p_id on the table
   141  // "parent", would require the following query:
   142  //
   143  // SELECT
   144  //   s.a_id, s.b_id, s.pk1, s.pk2
   145  // FROM
   146  //   (SELECT * FROM child@c_idx WHERE a_id IS NOT NULL AND b_id IS NOT NULL) AS s
   147  //   LEFT OUTER JOIN parent@p_idx AS t ON s.a_id = t.a AND s.b_id = t.b
   148  // WHERE
   149  //   t.a IS NULL
   150  // LIMIT 1  -- if limitResults is set
   151  //
   152  // TODO(radu): change this to a query which executes as an anti-join when we
   153  // remove the heuristic planner.
   154  func nonMatchingRowQuery(
   155  	srcTbl *sqlbase.TableDescriptor,
   156  	fk *sqlbase.ForeignKeyConstraint,
   157  	targetTbl *sqlbase.TableDescriptor,
   158  	limitResults bool,
   159  ) (sql string, originColNames []string, _ error) {
   160  	originColNames, err := srcTbl.NamesForColumnIDs(fk.OriginColumnIDs)
   161  	if err != nil {
   162  		return "", nil, err
   163  	}
   164  	// Get primary key columns not included in the FK
   165  	for _, pkColID := range srcTbl.PrimaryIndex.ColumnIDs {
   166  		found := false
   167  		for _, id := range fk.OriginColumnIDs {
   168  			if pkColID == id {
   169  				found = true
   170  				break
   171  			}
   172  		}
   173  		if !found {
   174  			column, err := srcTbl.FindActiveColumnByID(pkColID)
   175  			if err != nil {
   176  				return "", nil, err
   177  			}
   178  			originColNames = append(originColNames, column.Name)
   179  		}
   180  	}
   181  	srcCols := make([]string, len(originColNames))
   182  	qualifiedSrcCols := make([]string, len(originColNames))
   183  	for i, n := range originColNames {
   184  		srcCols[i] = tree.NameString(n)
   185  		// s is the table alias used in the query.
   186  		qualifiedSrcCols[i] = fmt.Sprintf("s.%s", srcCols[i])
   187  	}
   188  
   189  	referencedColNames, err := targetTbl.NamesForColumnIDs(fk.ReferencedColumnIDs)
   190  	if err != nil {
   191  		return "", nil, err
   192  	}
   193  	nCols := len(fk.OriginColumnIDs)
   194  	srcWhere := make([]string, nCols)
   195  	targetCols := make([]string, nCols)
   196  	on := make([]string, nCols)
   197  
   198  	for i := 0; i < nCols; i++ {
   199  		// s and t are table aliases used in the query
   200  		srcWhere[i] = fmt.Sprintf("%s IS NOT NULL", srcCols[i])
   201  		targetCols[i] = fmt.Sprintf("t.%s", tree.NameString(referencedColNames[i]))
   202  		on[i] = fmt.Sprintf("%s = %s", qualifiedSrcCols[i], targetCols[i])
   203  	}
   204  
   205  	limit := ""
   206  	if limitResults {
   207  		limit = " LIMIT 1"
   208  	}
   209  	return fmt.Sprintf(
   210  		`SELECT %[1]s FROM 
   211  		  (SELECT %[2]s FROM [%[3]d AS src]@{IGNORE_FOREIGN_KEYS} WHERE %[4]s) AS s
   212  			LEFT OUTER JOIN
   213  			(SELECT * FROM [%[5]d AS target]) AS t
   214  			ON %[6]s
   215  		 WHERE %[7]s IS NULL %[8]s`,
   216  		strings.Join(qualifiedSrcCols, ", "), // 1
   217  		strings.Join(srcCols, ", "),          // 2
   218  		srcTbl.ID,                            // 3
   219  		strings.Join(srcWhere, " AND "),      // 4
   220  		targetTbl.ID,                         // 5
   221  		strings.Join(on, " AND "),            // 6
   222  		// Sufficient to check the first column to see whether there was no matching row
   223  		targetCols[0], // 7
   224  		limit,         // 8
   225  	), originColNames, nil
   226  }
   227  
   228  // validateForeignKey verifies that all the rows in the srcTable
   229  // have a matching row in their referenced table.
   230  //
   231  // It operates entirely on the current goroutine and is thus able to
   232  // reuse an existing client.Txn safely.
   233  func validateForeignKey(
   234  	ctx context.Context,
   235  	srcTable *sqlbase.TableDescriptor,
   236  	fk *sqlbase.ForeignKeyConstraint,
   237  	ie *InternalExecutor,
   238  	txn *kv.Txn,
   239  	codec keys.SQLCodec,
   240  ) error {
   241  	targetTable, err := sqlbase.GetTableDescFromID(ctx, txn, codec, fk.ReferencedTableID)
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	nCols := len(fk.OriginColumnIDs)
   247  
   248  	referencedColumnNames, err := targetTable.NamesForColumnIDs(fk.ReferencedColumnIDs)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	// For MATCH FULL FKs, first check whether any disallowed keys containing both
   254  	// null and non-null values exist.
   255  	// (The matching options only matter for FKs with more than one column.)
   256  	if nCols > 1 && fk.Match == sqlbase.ForeignKeyReference_FULL {
   257  		query, colNames, err := matchFullUnacceptableKeyQuery(
   258  			srcTable, fk, true, /* limitResults */
   259  		)
   260  		if err != nil {
   261  			return err
   262  		}
   263  
   264  		log.Infof(ctx, "Validating MATCH FULL FK %q (%q [%v] -> %q [%v]) with query %q",
   265  			fk.Name,
   266  			srcTable.Name, colNames,
   267  			targetTable.Name, referencedColumnNames,
   268  			query,
   269  		)
   270  
   271  		values, err := ie.QueryRow(ctx, "validate foreign key constraint", txn, query)
   272  		if err != nil {
   273  			return err
   274  		}
   275  		if values.Len() > 0 {
   276  			return pgerror.Newf(pgcode.ForeignKeyViolation,
   277  				"foreign key violation: MATCH FULL does not allow mixing of null and nonnull values %s for %s",
   278  				formatValues(colNames, values), fk.Name,
   279  			)
   280  		}
   281  	}
   282  	query, colNames, err := nonMatchingRowQuery(
   283  		srcTable, fk, targetTable,
   284  		true, /* limitResults */
   285  	)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	log.Infof(ctx, "Validating FK %q (%q [%v] -> %q [%v]) with query %q",
   291  		fk.Name,
   292  		srcTable.Name, colNames, targetTable.Name, referencedColumnNames,
   293  		query,
   294  	)
   295  
   296  	values, err := ie.QueryRow(ctx, "validate fk constraint", txn, query)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	if values.Len() > 0 {
   301  		return pgerror.Newf(pgcode.ForeignKeyViolation,
   302  			"foreign key violation: %q row %s has no match in %q",
   303  			srcTable.Name, formatValues(colNames, values), targetTable.Name)
   304  	}
   305  	return nil
   306  }
   307  
   308  func formatValues(colNames []string, values tree.Datums) string {
   309  	var pairs bytes.Buffer
   310  	for i := range values {
   311  		if i > 0 {
   312  			pairs.WriteString(", ")
   313  		}
   314  		pairs.WriteString(fmt.Sprintf("%s=%v", colNames[i], values[i]))
   315  	}
   316  	return pairs.String()
   317  }
   318  
   319  // checkSet contains a subset of checks, as ordinals into
   320  // ImmutableTableDescriptor.ActiveChecks. These checks have boolean columns
   321  // produced as input to mutations, indicating the result of evaluating the
   322  // check.
   323  //
   324  // It is allowed to check only a subset of the active checks (the optimizer
   325  // could in principle determine that some checks can't fail because they
   326  // statically evaluate to true for the entire input).
   327  type checkSet = util.FastIntSet
   328  
   329  // When executing mutations, we calculate a boolean column for each check
   330  // indicating if the check passed. This function verifies that each result is
   331  // true or null.
   332  //
   333  // It is allowed to check only a subset of the active checks (for some, we could
   334  // determine that they can't fail because they statically evaluate to true for
   335  // the entire input); checkOrds contains the set of checks for which we have
   336  // values, as ordinals into ActiveChecks(). There must be exactly one value in
   337  // checkVals for each element in checkSet.
   338  func checkMutationInput(
   339  	tabDesc *sqlbase.ImmutableTableDescriptor, checkOrds checkSet, checkVals tree.Datums,
   340  ) error {
   341  	if len(checkVals) < checkOrds.Len() {
   342  		return errors.AssertionFailedf(
   343  			"mismatched check constraint columns: expected %d, got %d", checkOrds.Len(), len(checkVals))
   344  	}
   345  
   346  	checks := tabDesc.ActiveChecks()
   347  	colIdx := 0
   348  	for i := range checks {
   349  		if !checkOrds.Contains(i) {
   350  			continue
   351  		}
   352  
   353  		if res, err := tree.GetBool(checkVals[colIdx]); err != nil {
   354  			return err
   355  		} else if !res && checkVals[colIdx] != tree.DNull {
   356  			// Failed to satisfy CHECK constraint.
   357  			return pgerror.Newf(
   358  				pgcode.CheckViolation, "failed to satisfy CHECK constraint (%s)", checks[i].Expr,
   359  			)
   360  		}
   361  		colIdx++
   362  	}
   363  	return nil
   364  }