github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_update.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  )
    21  
    22  // fkExistenceCheckForUpdate is an auxiliary object with two purposes:
    23  //
    24  // - its main purpose is to facilitate the existence checks on both
    25  //   referencing and referenced tables when modifying rows in a table.
    26  //
    27  //   Note that users of this purpose are responsible for calling
    28  //   addCheckForIndex() on all mutated indexes, to register a mutated
    29  //   index for FK checking.
    30  //
    31  //   TODO(knz): why cannot the fkExistenceCheckForUpdate make this determination
    32  //   itself, like the other helpers? The asymmetry is concerning.
    33  //
    34  // - its secondary purpose is to serve the boolean "hasFk()" for "does
    35  //   the mutated table have any FK constraints, either forward or
    36  //   backward?"  This boolean is used by the row writer and the
    37  //   CASCADEing close.
    38  //
    39  //   TODO(knz): this responsibility should be carried by another
    40  //   object, so that the helper can specialize to only do existence
    41  //   checks!
    42  //
    43  type fkExistenceCheckForUpdate struct {
    44  	// inbound is responsible for existence checks in referencing tables.
    45  	inbound fkExistenceCheckForDelete
    46  	// output is responsible for existence checks in referenced tables.
    47  	outbound fkExistenceCheckForInsert
    48  
    49  	// indexIDsToCheck determines the list of indexes in the mutated
    50  	// table for which to perform FK checks.
    51  	//
    52  	// This may be a subset of all constraints on the mutated table.
    53  	// The inbound and outbound checkers look at all constraints by default;
    54  	// the update helper needs to maintain its own list of index IDs
    55  	// to operate on only a subset, and also define its own addIndexChecks()
    56  	// logic instead of deferring to addAllIdxChecks().
    57  	indexIDsToCheck map[sqlbase.IndexID]struct{}
    58  
    59  	// checker is the object that actually carries out the lookups in
    60  	// KV.
    61  	checker *fkExistenceBatchChecker
    62  }
    63  
    64  // makeFkExistenceCheckHelperForUpdate instantiates an update helper.
    65  func makeFkExistenceCheckHelperForUpdate(
    66  	ctx context.Context,
    67  	txn *kv.Txn,
    68  	codec keys.SQLCodec,
    69  	table *sqlbase.ImmutableTableDescriptor,
    70  	otherTables FkTableMetadata,
    71  	updateCols []sqlbase.ColumnDescriptor,
    72  	colMap map[sqlbase.ColumnID]int,
    73  	alloc *sqlbase.DatumAlloc,
    74  ) (fkExistenceCheckForUpdate, error) {
    75  	ret := fkExistenceCheckForUpdate{
    76  		indexIDsToCheck: map[sqlbase.IndexID]struct{}{0: {}},
    77  	}
    78  
    79  	// Instantiate a helper for the referencing tables.
    80  	var err error
    81  	if ret.inbound, err = makeFkExistenceCheckHelperForDelete(ctx, txn, codec, table, otherTables, colMap,
    82  		alloc); err != nil {
    83  		return ret, err
    84  	}
    85  
    86  	// Instantiate a helper for the referenced table(s).
    87  	ret.outbound, err = makeFkExistenceCheckHelperForInsert(ctx, txn, codec, table, otherTables, colMap, alloc)
    88  	ret.outbound.checker = ret.inbound.checker
    89  
    90  	// We need *some* KV batch checker to perform the checks. It doesn't
    91  	// matter which; so we use the one instantiated by the inbound
    92  	// checker and simply disregard/ignore the one instantiated by the
    93  	// outbound checker.
    94  	ret.checker = ret.inbound.checker
    95  
    96  	return ret, err
    97  }
    98  
    99  // addCheckForIndex registers a mutated index to perform FK existence checks for.
   100  func (fks fkExistenceCheckForUpdate) addCheckForIndex(
   101  	indexID sqlbase.IndexID, descriptorType sqlbase.IndexDescriptor_Type,
   102  ) {
   103  	if fks.checker == nil {
   104  		return
   105  	}
   106  	if descriptorType == sqlbase.IndexDescriptor_FORWARD {
   107  		// We ignore FK existence checks for inverted indexes.
   108  		//
   109  		// TODO(knz): verify that this is indeed correct.
   110  		fks.indexIDsToCheck[indexID] = struct{}{}
   111  	}
   112  }
   113  
   114  // hasFKs determines whether the table being mutated has any forward
   115  // or backward FK constraints. This is the secondary purpose of the helper
   116  // and is unrelated to the task of FK existence checks.
   117  func (fks fkExistenceCheckForUpdate) hasFKs() bool {
   118  	return len(fks.inbound.fks) > 0 || len(fks.outbound.fks) > 0
   119  }
   120  
   121  // addAllIdxChecks queues a FK existence check for the backward and forward
   122  // constraints for the indexes
   123  func (fks fkExistenceCheckForUpdate) addIndexChecks(
   124  	ctx context.Context, oldValues, newValues tree.Datums, traceKV bool,
   125  ) error {
   126  	for indexID := range fks.indexIDsToCheck {
   127  		if err := queueFkExistenceChecksForRow(ctx, fks.checker, fks.inbound.fks[indexID], oldValues, traceKV); err != nil {
   128  			return err
   129  		}
   130  		if err := queueFkExistenceChecksForRow(ctx, fks.checker, fks.outbound.fks[indexID], newValues, traceKV); err != nil {
   131  			return err
   132  		}
   133  	}
   134  	return nil
   135  }