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  }