github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/row/fk_existence_base.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  	"sort"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/keys"
    17  	"github.com/cockroachdb/cockroach/pkg/kv"
    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/sql/span"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    23  	"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  // fkExistenceCheckBaseHelper is an auxiliary struct that facilitates FK existence
    28  // checks for one FK constraint.
    29  //
    30  // TODO(knz): the fact that this captures the txn is problematic. The
    31  // txn should be passed as argument.
    32  type fkExistenceCheckBaseHelper struct {
    33  	// txn is the current KV transaction.
    34  	txn *kv.Txn
    35  
    36  	// dir indicates the direction of the check.
    37  	//
    38  	// Note that the helper is used both for forward checks (when
    39  	// INSERTing data in a *referencing* table) and backward checks
    40  	// (when DELETEing data in a *referenced* table). UPDATE uses
    41  	// helpers in both directions.
    42  	//
    43  	// Because it serves both directions, the explanations below
    44  	// avoid using the words "referencing" and "referenced". Instead
    45  	// it uses "searched" for the table/index where the existence
    46  	// is tested; and "mutated" for the table/index being written to.
    47  	//
    48  	dir FKCheckType
    49  
    50  	// rf is the row fetcher used to look up rows in the searched table.
    51  	rf *Fetcher
    52  
    53  	// searchIdx is the index used for lookups over the searched table.
    54  	searchIdx *sqlbase.IndexDescriptor
    55  
    56  	// prefixLen is the number of columns being looked up. In the common
    57  	// case it matches the number of columns in searchIdx, however it is
    58  	// possible to run a lookup check for a prefix of the columns in
    59  	// searchIdx, eg. `(a,b) REFERENCES t(x,y)` with an index on
    60  	// `(x,y,z)`.
    61  	prefixLen int
    62  
    63  	// ids maps column IDs in index searchIdx to positions of the `row`
    64  	// array provided to each FK existence check. This tells the checker
    65  	// where to find the values in the row for each column of the
    66  	// searched index.
    67  	ids map[sqlbase.ColumnID]int
    68  
    69  	// ref is a copy of the ForeignKeyConstraint object in the table
    70  	// descriptor.  During the check this is used to decide how to check
    71  	// the value (MATCH style).
    72  	//
    73  	// TODO(knz): the entire reference object is not needed during the
    74  	// mutation, only the match style and name. Simplify this.
    75  	ref *sqlbase.ForeignKeyConstraint
    76  
    77  	// searchTable is the descriptor of the searched table. Stored only
    78  	// for error messages; lookups use the pre-computed searchPrefix.
    79  	searchTable *sqlbase.ImmutableTableDescriptor
    80  
    81  	// mutatedIdx is the descriptor for the target index being mutated.
    82  	// Stored only for error messages.
    83  	mutatedIdx *sqlbase.IndexDescriptor
    84  
    85  	// valuesScratch is memory used to populate an error message when the check
    86  	// fails.
    87  	valuesScratch tree.Datums
    88  
    89  	// spanBuilder is responsible for constructing spans for FK lookups.
    90  	spanBuilder *span.Builder
    91  }
    92  
    93  // makeFkExistenceCheckBaseHelper instantiates a FK helper.
    94  //
    95  // - dir is the direction of the check.
    96  //
    97  // - ref is a copy of the FK constraint object that points
    98  //   to the table where to perform the existence check.
    99  //
   100  //   For forward checks, this is a copy of the FK
   101  //   constraint placed on the referencing table.
   102  //   For backward checks, this is a copy of the FK
   103  //   constraint placed as backref on the referenced table.
   104  //
   105  //   This is used to derive the searched table/index,
   106  //   and determine the MATCH style.
   107  //
   108  // - writeIdx is the target index being mutated. This is used
   109  //   to determine prefixLen in combination with searchIdx.
   110  //
   111  // - colMap maps column IDs in the searched index, to positions
   112  //   in the input `row` of datums during the check.
   113  //
   114  // - alloc is a suitable datum allocator used to initialize
   115  //   the row fetcher.
   116  //
   117  // - otherTables is an object that provides schema extraction services.
   118  //   TODO(knz): this should become homogeneous across the 3 packages
   119  //   sql, sqlbase, row. The proliferation is annoying.
   120  func makeFkExistenceCheckBaseHelper(
   121  	txn *kv.Txn,
   122  	codec keys.SQLCodec,
   123  	otherTables FkTableMetadata,
   124  	ref *sqlbase.ForeignKeyConstraint,
   125  	searchIdx *sqlbase.IndexDescriptor,
   126  	mutatedIdx *sqlbase.IndexDescriptor,
   127  	colMap map[sqlbase.ColumnID]int,
   128  	alloc *sqlbase.DatumAlloc,
   129  	dir FKCheckType,
   130  ) (ret fkExistenceCheckBaseHelper, err error) {
   131  	// Look up the searched table.
   132  	searchTable := otherTables[ref.ReferencedTableID].Desc
   133  	if searchTable == nil {
   134  		return ret, errors.AssertionFailedf("referenced table %d not in provided table map %+v", ref.ReferencedTableID, otherTables)
   135  	}
   136  	// Determine the columns being looked up.
   137  	ids, err := computeFkCheckColumnIDs(ref, mutatedIdx, searchIdx, colMap)
   138  	if err != nil {
   139  		return ret, err
   140  	}
   141  
   142  	// Initialize the row fetcher.
   143  	tableArgs := FetcherTableArgs{
   144  		Desc:             searchTable,
   145  		Index:            searchIdx,
   146  		ColIdxMap:        searchTable.ColumnIdxMap(),
   147  		IsSecondaryIndex: searchIdx.ID != searchTable.PrimaryIndex.ID,
   148  		Cols:             searchTable.Columns,
   149  	}
   150  	rf := &Fetcher{}
   151  	if err := rf.Init(
   152  		codec,
   153  		false, /* reverse */
   154  		sqlbase.ScanLockingStrength_FOR_NONE,
   155  		false, /* returnRangeInfo */
   156  		false, /* isCheck */
   157  		alloc,
   158  		tableArgs,
   159  	); err != nil {
   160  		return ret, err
   161  	}
   162  
   163  	return fkExistenceCheckBaseHelper{
   164  		txn:           txn,
   165  		dir:           dir,
   166  		rf:            rf,
   167  		ref:           ref,
   168  		searchTable:   searchTable,
   169  		searchIdx:     searchIdx,
   170  		mutatedIdx:    mutatedIdx,
   171  		ids:           ids,
   172  		prefixLen:     len(ref.OriginColumnIDs),
   173  		valuesScratch: make(tree.Datums, len(ref.OriginColumnIDs)),
   174  		spanBuilder:   span.MakeBuilder(codec, searchTable.TableDesc(), searchIdx),
   175  	}, nil
   176  }
   177  
   178  // computeFkCheckColumnIDs determines the set of column IDs to use for
   179  // the existence check, depending on the MATCH style.
   180  //
   181  // See https://github.com/cockroachdb/cockroach/issues/20305 or
   182  // https://www.postgresql.org/docs/11/sql-createtable.html for details on the
   183  // different composite foreign key matching methods.
   184  func computeFkCheckColumnIDs(
   185  	ref *sqlbase.ForeignKeyConstraint,
   186  	mutatedIdx *sqlbase.IndexDescriptor,
   187  	searchIdx *sqlbase.IndexDescriptor,
   188  	colMap map[sqlbase.ColumnID]int,
   189  ) (ids map[sqlbase.ColumnID]int, err error) {
   190  	ids = make(map[sqlbase.ColumnID]int, len(ref.OriginColumnIDs))
   191  
   192  	switch ref.Match {
   193  	case sqlbase.ForeignKeyReference_SIMPLE:
   194  		for i, writeColID := range ref.OriginColumnIDs {
   195  			if found, ok := colMap[writeColID]; ok {
   196  				ids[searchIdx.ColumnIDs[i]] = found
   197  			} else {
   198  				return nil, errSkipUnusedFK
   199  			}
   200  		}
   201  		return ids, nil
   202  
   203  	case sqlbase.ForeignKeyReference_FULL:
   204  		var missingColumns []string
   205  		for _, writeColID := range ref.OriginColumnIDs {
   206  			colOrdinal := -1
   207  			for i, colID := range mutatedIdx.ColumnIDs {
   208  				if writeColID == colID {
   209  					colOrdinal = i
   210  					break
   211  				}
   212  			}
   213  			if colOrdinal == -1 {
   214  				return nil, errors.AssertionFailedf("index %q on columns %+v does not contain column %d",
   215  					mutatedIdx.Name, mutatedIdx.ColumnIDs, writeColID)
   216  			}
   217  			if found, ok := colMap[writeColID]; ok {
   218  				ids[searchIdx.ColumnIDs[colOrdinal]] = found
   219  			} else {
   220  				missingColumns = append(missingColumns, mutatedIdx.ColumnNames[colOrdinal])
   221  			}
   222  		}
   223  
   224  		switch len(missingColumns) {
   225  		case 0:
   226  			return ids, nil
   227  
   228  		case 1:
   229  			return nil, pgerror.Newf(pgcode.ForeignKeyViolation,
   230  				"missing value for column %q in multi-part foreign key", missingColumns[0])
   231  
   232  		case len(ref.OriginColumnIDs):
   233  			// All the columns are nulls, don't check the foreign key.
   234  			return nil, errSkipUnusedFK
   235  
   236  		default:
   237  			sort.Strings(missingColumns)
   238  			return nil, pgerror.Newf(pgcode.ForeignKeyViolation,
   239  				"missing values for columns %q in multi-part foreign key", missingColumns)
   240  		}
   241  
   242  	case sqlbase.ForeignKeyReference_PARTIAL:
   243  		return nil, unimplemented.NewWithIssue(20305, "MATCH PARTIAL not supported")
   244  
   245  	default:
   246  		return nil, errors.AssertionFailedf("unknown composite key match type: %v", ref.Match)
   247  	}
   248  }
   249  
   250  var errSkipUnusedFK = errors.New("no columns involved in FK included in writer")