github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_insert.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  // fkExistenceCheckForInsert is an auxiliary object that facilitates the existence
    24  // checks on the referenced table when inserting new rows in
    25  // a referencing table.
    26  type fkExistenceCheckForInsert struct {
    27  	// fks maps mutated index id to slice of fkExistenceCheckBaseHelper, the outgoing
    28  	// foreign key existence checkers for each mutated index.
    29  	//
    30  	// In an fkInsertHelper, these slices will have at most one entry,
    31  	// since there can be (currently) at most one outgoing foreign key
    32  	// per mutated index. We use this data structure instead of a
    33  	// one-to-one map for consistency with the other helpers.
    34  	//
    35  	// TODO(knz): this limitation in CockroachDB is arbitrary and
    36  	// incompatible with PostgreSQL. pg supports potentially multiple
    37  	// referencing FK constraints for a single column tuple.
    38  	fks map[sqlbase.IndexID][]fkExistenceCheckBaseHelper
    39  
    40  	// checker is the object that actually carries out the lookups in
    41  	// KV.
    42  	checker *fkExistenceBatchChecker
    43  }
    44  
    45  // makeFkExistenceCheckHelperForInsert instantiates an insert helper.
    46  func makeFkExistenceCheckHelperForInsert(
    47  	ctx context.Context,
    48  	txn *kv.Txn,
    49  	codec keys.SQLCodec,
    50  	table *sqlbase.ImmutableTableDescriptor,
    51  	otherTables FkTableMetadata,
    52  	colMap map[sqlbase.ColumnID]int,
    53  	alloc *sqlbase.DatumAlloc,
    54  ) (fkExistenceCheckForInsert, error) {
    55  	h := fkExistenceCheckForInsert{
    56  		checker: &fkExistenceBatchChecker{
    57  			txn: txn,
    58  		},
    59  	}
    60  
    61  	// We need an existence check helper for every referenced table.
    62  	for i := range table.OutboundFKs {
    63  		ref := &table.OutboundFKs[i]
    64  		// Look up the searched table.
    65  		searchTable := otherTables[ref.ReferencedTableID].Desc
    66  		if searchTable == nil {
    67  			return h, errors.AssertionFailedf("referenced table %d not in provided table map %+v", ref.ReferencedTableID,
    68  				otherTables)
    69  		}
    70  		searchIdx, err := sqlbase.FindFKReferencedIndex(searchTable.TableDesc(), ref.ReferencedColumnIDs)
    71  		if err != nil {
    72  			return h, errors.NewAssertionErrorWithWrappedErrf(err,
    73  				"failed to find suitable search index for fk %q", ref.Name)
    74  		}
    75  		mutatedIdx, err := sqlbase.FindFKOriginIndex(table.TableDesc(), ref.OriginColumnIDs)
    76  		if err != nil {
    77  			// TODO (rohany): Remove once #48224 is resolved.
    78  			assertionError := errors.NewAssertionErrorWithWrappedErrf(err,
    79  				"failed to find suitable search index for fk %q", ref.Name)
    80  			issueLink := errors.IssueLink{IssueURL: "https://github.com/cockroachdb/cockroach/issues/48224"}
    81  			withLink := errors.WithIssueLink(assertionError, issueLink)
    82  			return h, withLink
    83  		}
    84  		fk, err := makeFkExistenceCheckBaseHelper(txn, codec, otherTables, ref, searchIdx, mutatedIdx, colMap, alloc, CheckInserts)
    85  		if errors.Is(err, errSkipUnusedFK) {
    86  			continue
    87  		}
    88  		if err != nil {
    89  			return h, err
    90  		}
    91  		if h.fks == nil {
    92  			h.fks = make(map[sqlbase.IndexID][]fkExistenceCheckBaseHelper)
    93  		}
    94  		h.fks[mutatedIdx.ID] = append(h.fks[mutatedIdx.ID], fk)
    95  	}
    96  
    97  	if len(h.fks) > 0 {
    98  		// TODO(knz,radu): FK existence checks need to see the writes
    99  		// performed by the mutation.
   100  		//
   101  		// In order to make this true, we need to split the existence
   102  		// checks into a separate sequencing step, and have the first
   103  		// check happen no early than the end of all the "main" part of
   104  		// the statement. Unfortunately, the organization of the code does
   105  		// not allow this today.
   106  		//
   107  		// See: https://github.com/cockroachdb/cockroach/issues/33475
   108  		//
   109  		// In order to "make do" and preserve a modicum of FK semantics we
   110  		// thus need to disable step-wise execution here. The result is that
   111  		// it will also enable any interleaved read part to observe the
   112  		// mutation, and thus introduce the risk of a Halloween problem for
   113  		// any mutation that uses FK relationships.
   114  		_ = txn.ConfigureStepping(ctx, kv.SteppingDisabled)
   115  	}
   116  
   117  	return h, nil
   118  }
   119  
   120  // addAllIdxChecks queues a FK existence check for every referenced table.
   121  func (h fkExistenceCheckForInsert) addAllIdxChecks(
   122  	ctx context.Context, row tree.Datums, traceKV bool,
   123  ) error {
   124  	for idx := range h.fks {
   125  		if err := queueFkExistenceChecksForRow(ctx, h.checker, h.fks[idx], row, traceKV); err != nil {
   126  			return err
   127  		}
   128  	}
   129  	return nil
   130  }