github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_delete.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/keys" 17 "github.com/cockroachdb/cockroach/pkg/kv" 18 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 19 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 20 "github.com/cockroachdb/errors" 21 ) 22 23 // fkExistenceCheckForDelete is an auxiliary object that facilitates the 24 // existence checks on the referencing table when deleting rows in a 25 // referenced table. 26 type fkExistenceCheckForDelete struct { 27 // fks maps mutated index id to slice of fkExistenceCheckBaseHelper, which 28 // performs FK existence checks in referencing tables. 29 fks map[sqlbase.IndexID][]fkExistenceCheckBaseHelper 30 31 // checker is the object that actually carries out the lookups in 32 // KV. 33 checker *fkExistenceBatchChecker 34 } 35 36 // makeFkExistenceCheckHelperForDelete instantiates a delete helper. 37 func makeFkExistenceCheckHelperForDelete( 38 ctx context.Context, 39 txn *kv.Txn, 40 codec keys.SQLCodec, 41 table *sqlbase.ImmutableTableDescriptor, 42 otherTables FkTableMetadata, 43 colMap map[sqlbase.ColumnID]int, 44 alloc *sqlbase.DatumAlloc, 45 ) (fkExistenceCheckForDelete, error) { 46 h := fkExistenceCheckForDelete{ 47 checker: &fkExistenceBatchChecker{ 48 txn: txn, 49 }, 50 } 51 52 // We need an existence check helper for every referencing 53 // table. 54 for i := range table.InboundFKs { 55 ref := &table.InboundFKs[i] 56 originTable := otherTables[ref.OriginTableID] 57 if originTable.IsAdding { 58 // We can assume that a table being added but not yet public is empty, 59 // and thus does not need to be checked for FK violations. 60 continue 61 } 62 // TODO(jordan,radu): this is busted, rip out when HP is removed. 63 // Fake a forward foreign key constraint. The HP requires an index on the 64 // reverse table, which won't be required by the CBO. So in HP, fail if we 65 // don't have this precondition. 66 // This will never be on an actual table descriptor, so we don't need to 67 // populate all the legacy index fields. 68 fakeRef := &sqlbase.ForeignKeyConstraint{ 69 ReferencedTableID: ref.OriginTableID, 70 ReferencedColumnIDs: ref.OriginColumnIDs, 71 OriginTableID: ref.ReferencedTableID, 72 OriginColumnIDs: ref.ReferencedColumnIDs, 73 // N.B.: Back-references always must have SIMPLE match method, because ... TODO(jordan): !!! 74 Match: sqlbase.ForeignKeyReference_SIMPLE, 75 OnDelete: ref.OnDelete, 76 OnUpdate: ref.OnUpdate, 77 } 78 searchIdx, err := sqlbase.FindFKOriginIndex(originTable.Desc.TableDesc(), ref.OriginColumnIDs) 79 if err != nil { 80 // TODO (rohany): Remove once #48224 is resolved. 81 assertionError := errors.NewAssertionErrorWithWrappedErrf( 82 err, "failed to find a suitable index on table %d for deletion", ref.OriginTableID) 83 issueLink := errors.IssueLink{IssueURL: "https://github.com/cockroachdb/cockroach/issues/48224"} 84 withLink := errors.WithIssueLink(assertionError, issueLink) 85 return fkExistenceCheckForDelete{}, withLink 86 } 87 mutatedIdx, err := sqlbase.FindFKReferencedIndex(table.TableDesc(), ref.ReferencedColumnIDs) 88 if err != nil { 89 return fkExistenceCheckForDelete{}, errors.NewAssertionErrorWithWrappedErrf( 90 err, "failed to find a suitable index on table %d for deletion", ref.ReferencedTableID) 91 } 92 fk, err := makeFkExistenceCheckBaseHelper(txn, codec, otherTables, fakeRef, searchIdx, mutatedIdx, colMap, alloc, 93 CheckDeletes) 94 if errors.Is(err, errSkipUnusedFK) { 95 continue 96 } 97 if err != nil { 98 return fkExistenceCheckForDelete{}, err 99 } 100 if h.fks == nil { 101 h.fks = make(map[sqlbase.IndexID][]fkExistenceCheckBaseHelper) 102 } 103 h.fks[mutatedIdx.ID] = append(h.fks[mutatedIdx.ID], fk) 104 } 105 106 if len(h.fks) > 0 { 107 // TODO(knz,radu): FK existence checks need to see the writes 108 // performed by the mutation. 109 // 110 // In order to make this true, we need to split the existence 111 // checks into a separate sequencing step, and have the first 112 // check happen no early than the end of all the "main" part of 113 // the statement. Unfortunately, the organization of the code does 114 // not allow this today. 115 // 116 // See: https://github.com/cockroachdb/cockroach/issues/33475 117 // 118 // In order to "make do" and preserve a modicum of FK semantics we 119 // thus need to disable step-wise execution here. The result is that 120 // it will also enable any interleaved read part to observe the 121 // mutation, and thus introduce the risk of a Halloween problem for 122 // any mutation that uses FK relationships. 123 _ = txn.ConfigureStepping(ctx, kv.SteppingDisabled) 124 } 125 126 return h, nil 127 } 128 129 // addAllIdxChecks queues a FK existence check for every referencing table. 130 func (h fkExistenceCheckForDelete) addAllIdxChecks( 131 ctx context.Context, row tree.Datums, traceKV bool, 132 ) error { 133 for idx := range h.fks { 134 if err := queueFkExistenceChecksForRow(ctx, h.checker, h.fks[idx], row, traceKV); err != nil { 135 return err 136 } 137 } 138 return nil 139 }