github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_base.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 "sort" 15 16 "github.com/cockroachdb/cockroach/pkg/keys" 17 "github.com/cockroachdb/cockroach/pkg/kv" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 20 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 21 "github.com/cockroachdb/cockroach/pkg/sql/span" 22 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 23 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 24 "github.com/cockroachdb/errors" 25 ) 26 27 // fkExistenceCheckBaseHelper is an auxiliary struct that facilitates FK existence 28 // checks for one FK constraint. 29 // 30 // TODO(knz): the fact that this captures the txn is problematic. The 31 // txn should be passed as argument. 32 type fkExistenceCheckBaseHelper struct { 33 // txn is the current KV transaction. 34 txn *kv.Txn 35 36 // dir indicates the direction of the check. 37 // 38 // Note that the helper is used both for forward checks (when 39 // INSERTing data in a *referencing* table) and backward checks 40 // (when DELETEing data in a *referenced* table). UPDATE uses 41 // helpers in both directions. 42 // 43 // Because it serves both directions, the explanations below 44 // avoid using the words "referencing" and "referenced". Instead 45 // it uses "searched" for the table/index where the existence 46 // is tested; and "mutated" for the table/index being written to. 47 // 48 dir FKCheckType 49 50 // rf is the row fetcher used to look up rows in the searched table. 51 rf *Fetcher 52 53 // searchIdx is the index used for lookups over the searched table. 54 searchIdx *sqlbase.IndexDescriptor 55 56 // prefixLen is the number of columns being looked up. In the common 57 // case it matches the number of columns in searchIdx, however it is 58 // possible to run a lookup check for a prefix of the columns in 59 // searchIdx, eg. `(a,b) REFERENCES t(x,y)` with an index on 60 // `(x,y,z)`. 61 prefixLen int 62 63 // ids maps column IDs in index searchIdx to positions of the `row` 64 // array provided to each FK existence check. This tells the checker 65 // where to find the values in the row for each column of the 66 // searched index. 67 ids map[sqlbase.ColumnID]int 68 69 // ref is a copy of the ForeignKeyConstraint object in the table 70 // descriptor. During the check this is used to decide how to check 71 // the value (MATCH style). 72 // 73 // TODO(knz): the entire reference object is not needed during the 74 // mutation, only the match style and name. Simplify this. 75 ref *sqlbase.ForeignKeyConstraint 76 77 // searchTable is the descriptor of the searched table. Stored only 78 // for error messages; lookups use the pre-computed searchPrefix. 79 searchTable *sqlbase.ImmutableTableDescriptor 80 81 // mutatedIdx is the descriptor for the target index being mutated. 82 // Stored only for error messages. 83 mutatedIdx *sqlbase.IndexDescriptor 84 85 // valuesScratch is memory used to populate an error message when the check 86 // fails. 87 valuesScratch tree.Datums 88 89 // spanBuilder is responsible for constructing spans for FK lookups. 90 spanBuilder *span.Builder 91 } 92 93 // makeFkExistenceCheckBaseHelper instantiates a FK helper. 94 // 95 // - dir is the direction of the check. 96 // 97 // - ref is a copy of the FK constraint object that points 98 // to the table where to perform the existence check. 99 // 100 // For forward checks, this is a copy of the FK 101 // constraint placed on the referencing table. 102 // For backward checks, this is a copy of the FK 103 // constraint placed as backref on the referenced table. 104 // 105 // This is used to derive the searched table/index, 106 // and determine the MATCH style. 107 // 108 // - writeIdx is the target index being mutated. This is used 109 // to determine prefixLen in combination with searchIdx. 110 // 111 // - colMap maps column IDs in the searched index, to positions 112 // in the input `row` of datums during the check. 113 // 114 // - alloc is a suitable datum allocator used to initialize 115 // the row fetcher. 116 // 117 // - otherTables is an object that provides schema extraction services. 118 // TODO(knz): this should become homogeneous across the 3 packages 119 // sql, sqlbase, row. The proliferation is annoying. 120 func makeFkExistenceCheckBaseHelper( 121 txn *kv.Txn, 122 codec keys.SQLCodec, 123 otherTables FkTableMetadata, 124 ref *sqlbase.ForeignKeyConstraint, 125 searchIdx *sqlbase.IndexDescriptor, 126 mutatedIdx *sqlbase.IndexDescriptor, 127 colMap map[sqlbase.ColumnID]int, 128 alloc *sqlbase.DatumAlloc, 129 dir FKCheckType, 130 ) (ret fkExistenceCheckBaseHelper, err error) { 131 // Look up the searched table. 132 searchTable := otherTables[ref.ReferencedTableID].Desc 133 if searchTable == nil { 134 return ret, errors.AssertionFailedf("referenced table %d not in provided table map %+v", ref.ReferencedTableID, otherTables) 135 } 136 // Determine the columns being looked up. 137 ids, err := computeFkCheckColumnIDs(ref, mutatedIdx, searchIdx, colMap) 138 if err != nil { 139 return ret, err 140 } 141 142 // Initialize the row fetcher. 143 tableArgs := FetcherTableArgs{ 144 Desc: searchTable, 145 Index: searchIdx, 146 ColIdxMap: searchTable.ColumnIdxMap(), 147 IsSecondaryIndex: searchIdx.ID != searchTable.PrimaryIndex.ID, 148 Cols: searchTable.Columns, 149 } 150 rf := &Fetcher{} 151 if err := rf.Init( 152 codec, 153 false, /* reverse */ 154 sqlbase.ScanLockingStrength_FOR_NONE, 155 false, /* returnRangeInfo */ 156 false, /* isCheck */ 157 alloc, 158 tableArgs, 159 ); err != nil { 160 return ret, err 161 } 162 163 return fkExistenceCheckBaseHelper{ 164 txn: txn, 165 dir: dir, 166 rf: rf, 167 ref: ref, 168 searchTable: searchTable, 169 searchIdx: searchIdx, 170 mutatedIdx: mutatedIdx, 171 ids: ids, 172 prefixLen: len(ref.OriginColumnIDs), 173 valuesScratch: make(tree.Datums, len(ref.OriginColumnIDs)), 174 spanBuilder: span.MakeBuilder(codec, searchTable.TableDesc(), searchIdx), 175 }, nil 176 } 177 178 // computeFkCheckColumnIDs determines the set of column IDs to use for 179 // the existence check, depending on the MATCH style. 180 // 181 // See https://github.com/cockroachdb/cockroach/issues/20305 or 182 // https://www.postgresql.org/docs/11/sql-createtable.html for details on the 183 // different composite foreign key matching methods. 184 func computeFkCheckColumnIDs( 185 ref *sqlbase.ForeignKeyConstraint, 186 mutatedIdx *sqlbase.IndexDescriptor, 187 searchIdx *sqlbase.IndexDescriptor, 188 colMap map[sqlbase.ColumnID]int, 189 ) (ids map[sqlbase.ColumnID]int, err error) { 190 ids = make(map[sqlbase.ColumnID]int, len(ref.OriginColumnIDs)) 191 192 switch ref.Match { 193 case sqlbase.ForeignKeyReference_SIMPLE: 194 for i, writeColID := range ref.OriginColumnIDs { 195 if found, ok := colMap[writeColID]; ok { 196 ids[searchIdx.ColumnIDs[i]] = found 197 } else { 198 return nil, errSkipUnusedFK 199 } 200 } 201 return ids, nil 202 203 case sqlbase.ForeignKeyReference_FULL: 204 var missingColumns []string 205 for _, writeColID := range ref.OriginColumnIDs { 206 colOrdinal := -1 207 for i, colID := range mutatedIdx.ColumnIDs { 208 if writeColID == colID { 209 colOrdinal = i 210 break 211 } 212 } 213 if colOrdinal == -1 { 214 return nil, errors.AssertionFailedf("index %q on columns %+v does not contain column %d", 215 mutatedIdx.Name, mutatedIdx.ColumnIDs, writeColID) 216 } 217 if found, ok := colMap[writeColID]; ok { 218 ids[searchIdx.ColumnIDs[colOrdinal]] = found 219 } else { 220 missingColumns = append(missingColumns, mutatedIdx.ColumnNames[colOrdinal]) 221 } 222 } 223 224 switch len(missingColumns) { 225 case 0: 226 return ids, nil 227 228 case 1: 229 return nil, pgerror.Newf(pgcode.ForeignKeyViolation, 230 "missing value for column %q in multi-part foreign key", missingColumns[0]) 231 232 case len(ref.OriginColumnIDs): 233 // All the columns are nulls, don't check the foreign key. 234 return nil, errSkipUnusedFK 235 236 default: 237 sort.Strings(missingColumns) 238 return nil, pgerror.Newf(pgcode.ForeignKeyViolation, 239 "missing values for columns %q in multi-part foreign key", missingColumns) 240 } 241 242 case sqlbase.ForeignKeyReference_PARTIAL: 243 return nil, unimplemented.NewWithIssue(20305, "MATCH PARTIAL not supported") 244 245 default: 246 return nil, errors.AssertionFailedf("unknown composite key match type: %v", ref.Match) 247 } 248 } 249 250 var errSkipUnusedFK = errors.New("no columns involved in FK included in writer")