github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_batch.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/kv" 17 "github.com/cockroachdb/cockroach/pkg/roachpb" 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/util/log" 22 "github.com/cockroachdb/errors" 23 ) 24 25 // fkExistenceBatchChecker accumulates foreign key existence checks and sends 26 // them out as a single kv batch on demand. Checks are accumulated in 27 // order - the first failing check will be the one that produces an 28 // error report. 29 type fkExistenceBatchChecker struct { 30 // txn captures the current transaction. 31 // 32 // TODO(knz): Don't do this. txn objects, like contexts, 33 // should not be captured in structs. 34 txn *kv.Txn 35 36 // batch is the accumulated batch of existence checks so far. 37 batch roachpb.BatchRequest 38 39 // batchIdxToFk maps the index of the check request/response in the kv batch 40 // to the fkExistenceCheckBaseHelper that created it. 41 batchIdxToFk []*fkExistenceCheckBaseHelper 42 } 43 44 // reset starts a new batch. 45 func (f *fkExistenceBatchChecker) reset() { 46 f.batch.Reset() 47 f.batchIdxToFk = f.batchIdxToFk[:0] 48 } 49 50 // addCheck adds a check for the given row and fkExistenceCheckBaseHelper to the batch. 51 func (f *fkExistenceBatchChecker) addCheck( 52 ctx context.Context, row tree.Datums, source *fkExistenceCheckBaseHelper, traceKV bool, 53 ) error { 54 span, err := source.spanForValues(row) 55 if err != nil { 56 return err 57 } 58 scan := roachpb.ScanRequest{ 59 RequestHeader: roachpb.RequestHeaderFromSpan(span), 60 } 61 if traceKV { 62 log.VEventf(ctx, 2, "FKScan %s", span) 63 } 64 f.batch.Requests = append(f.batch.Requests, roachpb.RequestUnion{}) 65 f.batch.Requests[len(f.batch.Requests)-1].MustSetInner(&scan) 66 f.batchIdxToFk = append(f.batchIdxToFk, source) 67 return nil 68 } 69 70 // runCheck sends the accumulated batch of foreign key checks to kv, given the 71 // old and new values of the row being modified. Either oldRow or newRow can 72 // be set to nil in the case of an insert or a delete, respectively. 73 // A pgcode.ForeignKeyViolation is returned if a foreign key violation 74 // is detected, corresponding to the first foreign key that was violated in 75 // order of addition. 76 func (f *fkExistenceBatchChecker) runCheck( 77 ctx context.Context, oldRow tree.Datums, newRow tree.Datums, 78 ) error { 79 if len(f.batch.Requests) == 0 { 80 return nil 81 } 82 defer f.reset() 83 84 // Run the batch. 85 br, err := f.txn.Send(ctx, f.batch) 86 if err != nil { 87 return err.GoError() 88 } 89 90 // Process the responses. 91 fetcher := SpanKVFetcher{} 92 for i, resp := range br.Responses { 93 fk := f.batchIdxToFk[i] 94 fetcher.KVs = resp.GetInner().(*roachpb.ScanResponse).Rows 95 if err := fk.rf.StartScanFrom(ctx, &fetcher); err != nil { 96 return err 97 } 98 99 switch fk.dir { 100 case CheckInserts: 101 // If we're inserting, then there's a violation if the scan found nothing. 102 if fk.rf.kvEnd { 103 for valueIdx, colID := range fk.searchIdx.ColumnIDs[:fk.prefixLen] { 104 fk.valuesScratch[valueIdx] = newRow[fk.ids[colID]] 105 } 106 return pgerror.Newf(pgcode.ForeignKeyViolation, 107 "foreign key violation: value %s not found in %s@%s %s (txn=%s)", 108 fk.valuesScratch, fk.searchTable.Name, fk.searchIdx.Name, 109 fk.searchIdx.ColumnNames[:fk.prefixLen], f.txn.ID()) 110 } 111 112 case CheckDeletes: 113 // If we're deleting, then there's a violation if the scan found something. 114 if !fk.rf.kvEnd { 115 if oldRow == nil { 116 return pgerror.Newf(pgcode.ForeignKeyViolation, 117 "foreign key violation: non-empty columns %s referenced in table %q", 118 fk.mutatedIdx.ColumnNames[fk.prefixLen], fk.searchTable.Name) 119 } 120 121 for valueIdx, colID := range fk.searchIdx.ColumnIDs[:fk.prefixLen] { 122 fk.valuesScratch[valueIdx] = oldRow[fk.ids[colID]] 123 } 124 return pgerror.Newf(pgcode.ForeignKeyViolation, 125 "foreign key violation: values %v in columns %s referenced in table %q", 126 fk.valuesScratch, fk.mutatedIdx.ColumnNames[:fk.prefixLen], fk.searchTable.Name) 127 } 128 129 default: 130 return errors.AssertionFailedf("impossible case: fkExistenceCheckBaseHelper has dir=%v", fk.dir) 131 } 132 } 133 134 return nil 135 } 136 137 // SpanKVFetcher is a kvBatchFetcher that returns a set slice of kvs. 138 type SpanKVFetcher struct { 139 KVs []roachpb.KeyValue 140 } 141 142 // nextBatch implements the kvBatchFetcher interface. 143 func (f *SpanKVFetcher) nextBatch( 144 _ context.Context, 145 ) (ok bool, kvs []roachpb.KeyValue, batchResponse []byte, span roachpb.Span, err error) { 146 if len(f.KVs) == 0 { 147 return false, nil, nil, roachpb.Span{}, nil 148 } 149 res := f.KVs 150 f.KVs = nil 151 return true, res, nil, roachpb.Span{}, nil 152 } 153 154 // GetRangesInfo implements the kvBatchFetcher interface. 155 func (f *SpanKVFetcher) GetRangesInfo() []roachpb.RangeInfo { 156 panic(errors.AssertionFailedf("GetRangesInfo() called on SpanKVFetcher")) 157 }