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  }