github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_check.go (about) 1 // Copyright 2019 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 row 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 18 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 19 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 20 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // queueFkExistenceChecksForRow initiates FK existence checks for a 25 // given mutated row. 26 func queueFkExistenceChecksForRow( 27 ctx context.Context, 28 checkRunner *fkExistenceBatchChecker, 29 mutatedIdxHelpers []fkExistenceCheckBaseHelper, 30 mutatedRow tree.Datums, 31 traceKV bool, 32 ) error { 33 outer: 34 for i, fk := range mutatedIdxHelpers { 35 // See https://github.com/cockroachdb/cockroach/issues/20305 or 36 // https://www.postgresql.org/docs/11/sql-createtable.html for details on the 37 // different composite foreign key matching methods. 38 // 39 // TODO(knz): it is inefficient to do this dynamic dispatch based on 40 // the match type and column layout again for every row. Consider 41 // hoisting some of these checks to once per logical plan. 42 switch fk.ref.Match { 43 case sqlbase.ForeignKeyReference_SIMPLE: 44 for _, colID := range fk.searchIdx.ColumnIDs[:fk.prefixLen] { 45 found, ok := fk.ids[colID] 46 if !ok { 47 return errors.AssertionFailedf("fk ids (%v) missing column id %d", fk.ids, colID) 48 } 49 if mutatedRow[found] == tree.DNull { 50 continue outer 51 } 52 } 53 if err := checkRunner.addCheck(ctx, mutatedRow, &mutatedIdxHelpers[i], traceKV); err != nil { 54 return err 55 } 56 57 case sqlbase.ForeignKeyReference_FULL: 58 var nulls, notNulls bool 59 for _, colID := range fk.searchIdx.ColumnIDs[:fk.prefixLen] { 60 found, ok := fk.ids[colID] 61 if !ok { 62 return errors.AssertionFailedf("fk ids (%v) missing column id %d", fk.ids, colID) 63 } 64 if mutatedRow[found] == tree.DNull { 65 nulls = true 66 } else { 67 notNulls = true 68 } 69 } 70 if nulls && notNulls { 71 // TODO(bram): expand this error to show more details. 72 return pgerror.Newf(pgcode.ForeignKeyViolation, 73 "foreign key violation: MATCH FULL does not allow mixing of null and nonnull values %s for %s", 74 mutatedRow, fk.ref.Name, 75 ) 76 } 77 // Never check references for MATCH FULL that are all nulls. 78 if nulls { 79 continue 80 } 81 if err := checkRunner.addCheck(ctx, mutatedRow, &mutatedIdxHelpers[i], traceKV); err != nil { 82 return err 83 } 84 85 case sqlbase.ForeignKeyReference_PARTIAL: 86 return unimplemented.NewWithIssue(20305, "MATCH PARTIAL not supported") 87 88 default: 89 return errors.AssertionFailedf("unknown composite key match type: %v", fk.ref.Match) 90 } 91 } 92 return nil 93 }