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 }