github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/scrub_fk.go (about)

     1  // Copyright 2017 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 sql
    12  
    13  import (
    14  	"context"
    15  	"time"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/scrub"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    20  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    21  )
    22  
    23  // sqlForeignKeyCheckOperation is a check on an indexes physical data.
    24  type sqlForeignKeyCheckOperation struct {
    25  	tableName  *tree.TableName
    26  	tableDesc  *sqlbase.ImmutableTableDescriptor
    27  	constraint *sqlbase.ConstraintDetail
    28  	asOf       hlc.Timestamp
    29  
    30  	colIDToRowIdx map[sqlbase.ColumnID]int
    31  
    32  	run sqlForeignKeyConstraintCheckRun
    33  }
    34  
    35  // sqlForeignKeyConstraintCheckRun contains the run-time state for
    36  // sqlForeignKeyConstraintCheckOperation during local execution.
    37  type sqlForeignKeyConstraintCheckRun struct {
    38  	started  bool
    39  	rows     []tree.Datums
    40  	rowIndex int
    41  }
    42  
    43  func newSQLForeignKeyCheckOperation(
    44  	tableName *tree.TableName,
    45  	tableDesc *sqlbase.ImmutableTableDescriptor,
    46  	constraint sqlbase.ConstraintDetail,
    47  	asOf hlc.Timestamp,
    48  ) *sqlForeignKeyCheckOperation {
    49  	return &sqlForeignKeyCheckOperation{
    50  		tableName:  tableName,
    51  		tableDesc:  tableDesc,
    52  		constraint: &constraint,
    53  		asOf:       asOf,
    54  	}
    55  }
    56  
    57  // Start implements the checkOperation interface.
    58  // It creates a query string and generates a plan from it, which then
    59  // runs in the distSQL execution engine.
    60  func (o *sqlForeignKeyCheckOperation) Start(params runParams) error {
    61  	ctx := params.ctx
    62  
    63  	checkQuery, _, err := nonMatchingRowQuery(
    64  		&o.tableDesc.TableDescriptor,
    65  		o.constraint.FK,
    66  		o.constraint.ReferencedTable,
    67  		false, /* limitResults */
    68  	)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	rows, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.Query(
    74  		ctx, "scrub-fk", params.p.txn, checkQuery,
    75  	)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	o.run.rows = rows
    80  
    81  	if len(o.constraint.FK.OriginColumnIDs) > 1 && o.constraint.FK.Match == sqlbase.ForeignKeyReference_FULL {
    82  		// Check if there are any disallowed references where some columns are NULL
    83  		// and some aren't.
    84  		checkNullsQuery, _, err := matchFullUnacceptableKeyQuery(
    85  			&o.tableDesc.TableDescriptor,
    86  			o.constraint.FK,
    87  			false, /* limitResults */
    88  		)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		rows, err := params.extendedEvalCtx.ExecCfg.InternalExecutor.Query(
    93  			ctx, "scrub-fk", params.p.txn, checkNullsQuery,
    94  		)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		o.run.rows = append(o.run.rows, rows...)
    99  	}
   100  
   101  	// Collect the expected types for the query results. This is all
   102  	// columns and extra columns in the secondary index used for foreign
   103  	// key referencing. This also implicitly includes all primary index
   104  	// columns.
   105  	columnsByID := make(map[sqlbase.ColumnID]*sqlbase.ColumnDescriptor, len(o.tableDesc.Columns))
   106  	for i := range o.tableDesc.Columns {
   107  		columnsByID[o.tableDesc.Columns[i].ID] = &o.tableDesc.Columns[i]
   108  	}
   109  
   110  	// Get primary key columns not included in the FK.
   111  	var colIDs []sqlbase.ColumnID
   112  	colIDs = append(colIDs, o.constraint.FK.OriginColumnIDs...)
   113  	for _, pkColID := range o.tableDesc.PrimaryIndex.ColumnIDs {
   114  		found := false
   115  		for _, id := range o.constraint.FK.OriginColumnIDs {
   116  			if pkColID == id {
   117  				found = true
   118  				break
   119  			}
   120  		}
   121  		if !found {
   122  			colIDs = append(colIDs, pkColID)
   123  		}
   124  	}
   125  
   126  	o.colIDToRowIdx = make(map[sqlbase.ColumnID]int, len(colIDs))
   127  	for i, id := range colIDs {
   128  		o.colIDToRowIdx[id] = i
   129  	}
   130  
   131  	o.run.started = true
   132  	return nil
   133  }
   134  
   135  // Next implements the checkOperation interface.
   136  func (o *sqlForeignKeyCheckOperation) Next(params runParams) (tree.Datums, error) {
   137  	row := o.run.rows[o.run.rowIndex]
   138  	o.run.rowIndex++
   139  
   140  	details := make(map[string]interface{})
   141  	rowDetails := make(map[string]interface{})
   142  	details["row_data"] = rowDetails
   143  	details["constraint_name"] = o.constraint.FK.Name
   144  
   145  	// Collect the primary index values for generating the primary key
   146  	// pretty string.
   147  	primaryKeyDatums := make(tree.Datums, 0, len(o.tableDesc.PrimaryIndex.ColumnIDs))
   148  	for _, id := range o.tableDesc.PrimaryIndex.ColumnIDs {
   149  		idx := o.colIDToRowIdx[id]
   150  		primaryKeyDatums = append(primaryKeyDatums, row[idx])
   151  	}
   152  
   153  	// Collect all of the values fetched from the index to generate a
   154  	// pretty JSON dictionary for row_data.
   155  	for _, id := range o.constraint.FK.OriginColumnIDs {
   156  		idx := o.colIDToRowIdx[id]
   157  		col, err := o.tableDesc.FindActiveColumnByID(id)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		rowDetails[col.Name] = row[idx].String()
   162  	}
   163  	for _, id := range o.tableDesc.PrimaryIndex.ColumnIDs {
   164  		found := false
   165  		for _, fkID := range o.constraint.FK.OriginColumnIDs {
   166  			if id == fkID {
   167  				found = true
   168  				break
   169  			}
   170  		}
   171  		if !found {
   172  			idx := o.colIDToRowIdx[id]
   173  			col, err := o.tableDesc.FindActiveColumnByID(id)
   174  			if err != nil {
   175  				return nil, err
   176  			}
   177  			rowDetails[col.Name] = row[idx].String()
   178  		}
   179  	}
   180  
   181  	detailsJSON, err := tree.MakeDJSON(details)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	ts, err := tree.MakeDTimestamp(
   187  		params.extendedEvalCtx.GetStmtTimestamp(),
   188  		time.Nanosecond,
   189  	)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	return tree.Datums{
   195  		// TODO(joey): Add the job UUID once the SCRUB command uses jobs.
   196  		tree.DNull, /* job_uuid */
   197  		tree.NewDString(scrub.ForeignKeyConstraintViolation),
   198  		tree.NewDString(o.tableName.Catalog()),
   199  		tree.NewDString(o.tableName.Table()),
   200  		tree.NewDString(primaryKeyDatums.String()),
   201  		ts,
   202  		tree.DBoolFalse,
   203  		detailsJSON,
   204  	}, nil
   205  }
   206  
   207  // Started implements the checkOperation interface.
   208  func (o *sqlForeignKeyCheckOperation) Started() bool {
   209  	return o.run.started
   210  }
   211  
   212  // Done implements the checkOperation interface.
   213  func (o *sqlForeignKeyCheckOperation) Done(ctx context.Context) bool {
   214  	return o.run.rows == nil || o.run.rowIndex >= len(o.run.rows)
   215  }
   216  
   217  // Close implements the checkOperation interface.
   218  func (o *sqlForeignKeyCheckOperation) Close(ctx context.Context) {
   219  	o.run.rows = nil
   220  }