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 }