github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/foreign_key_editor.go (about) 1 // Copyright 2022 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plan 16 17 import ( 18 "fmt" 19 "io" 20 "strings" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 ) 24 25 // ChildParentMapping is a mapping from the foreign key columns of a child schema to the parent schema. The position 26 // in the slice corresponds to the position in the child schema, while the value at a given position refers to the 27 // position in the parent schema. For all columns that are not in the foreign key definition, a value of -1 is returned. 28 // 29 // Here's an example: 30 // parent Schema: x1, x2, x3, x4, x5 31 // child Schema: y1, y2, y3, y4 32 // FOREIGN KEY (y2) REFERENCES parent (x4) 33 // 34 // The slice for the above would be [-1, 3, -1, -1]. The foreign key uses the column "y2" on the child, which is the 35 // second position in the schema (and therefore the second position in the mapping). The equivalent parent column is 36 // "x4", which is in the fourth position (so 3 with zero-based indexed). 37 type ChildParentMapping []int 38 39 // ForeignKeyRefActionData contains the mapper, editor, and child to parent mapping for processing referential actions. 40 type ForeignKeyRefActionData struct { 41 RowMapper *ForeignKeyRowMapper 42 Editor *ForeignKeyEditor 43 ForeignKey sql.ForeignKeyConstraint 44 ChildParentMapping ChildParentMapping 45 } 46 47 // ForeignKeyEditor handles update and delete operations, as they may have referential actions on other tables (such as 48 // cascading). If this editor is Cyclical, then that means that following the referential actions will eventually lead 49 // back to this same editor. Self-referential foreign keys are inherently cyclical. 50 type ForeignKeyEditor struct { 51 Schema sql.Schema 52 Editor sql.ForeignKeyEditor 53 References []*ForeignKeyReferenceHandler 54 RefActions []ForeignKeyRefActionData 55 Cyclical bool 56 } 57 58 // IsInitialized returns whether this editor has been initialized. The given map is used to prevent cycles, as editors 59 // will reference themselves if a cycle is formed between foreign keys. 60 func (fkEditor *ForeignKeyEditor) IsInitialized(editors map[*ForeignKeyEditor]struct{}) bool { 61 if fkEditor == nil || fkEditor.Editor == nil { 62 return false 63 } 64 if _, ok := editors[fkEditor]; ok { 65 return true 66 } 67 editors[fkEditor] = struct{}{} 68 for _, reference := range fkEditor.References { 69 if !reference.IsInitialized() { 70 return false 71 } 72 } 73 for _, refAction := range fkEditor.RefActions { 74 if !refAction.Editor.IsInitialized(editors) { 75 return false 76 } 77 } 78 return true 79 } 80 81 // Update handles both the standard UPDATE statement and propagated referential actions from a parent table's ON UPDATE. 82 func (fkEditor *ForeignKeyEditor) Update(ctx *sql.Context, old sql.Row, new sql.Row, depth int) error { 83 for _, reference := range fkEditor.References { 84 if err := reference.CheckReference(ctx, new); err != nil { 85 return err 86 } 87 } 88 for _, refActionData := range fkEditor.RefActions { 89 switch refActionData.ForeignKey.OnUpdate { 90 default: // RESTRICT and friends 91 if err := fkEditor.OnUpdateRestrict(ctx, refActionData, old, new); err != nil { 92 return err 93 } 94 case sql.ForeignKeyReferentialAction_Cascade: 95 case sql.ForeignKeyReferentialAction_SetNull: 96 } 97 } 98 if err := fkEditor.Editor.Update(ctx, old, new); err != nil { 99 return err 100 } 101 for _, refActionData := range fkEditor.RefActions { 102 switch refActionData.ForeignKey.OnUpdate { 103 case sql.ForeignKeyReferentialAction_Cascade: 104 if err := fkEditor.OnUpdateCascade(ctx, refActionData, old, new, depth+1); err != nil { 105 return err 106 } 107 case sql.ForeignKeyReferentialAction_SetNull: 108 if err := fkEditor.OnUpdateSetNull(ctx, refActionData, old, new, depth+1); err != nil { 109 return err 110 } 111 } 112 } 113 return nil 114 } 115 116 // OnUpdateRestrict handles the ON UPDATE RESTRICT referential action. 117 func (fkEditor *ForeignKeyEditor) OnUpdateRestrict(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row) error { 118 if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil { 119 return err 120 } else if !ok { 121 return nil 122 } 123 124 rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false) 125 if err != nil { 126 return err 127 } 128 defer rowIter.Close(ctx) 129 if _, err = rowIter.Next(ctx); err == nil { 130 return sql.ErrForeignKeyParentViolation.New(refActionData.ForeignKey.Name, 131 refActionData.ForeignKey.Table, refActionData.ForeignKey.ParentTable, refActionData.RowMapper.GetKeyString(old)) 132 } 133 if err != io.EOF { 134 return err 135 } 136 return nil 137 } 138 139 // OnUpdateCascade handles the ON UPDATE CASCADE referential action. 140 func (fkEditor *ForeignKeyEditor) OnUpdateCascade(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error { 141 if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil { 142 return err 143 } else if !ok { 144 return nil 145 } 146 147 rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false) 148 if err != nil { 149 return err 150 } 151 defer rowIter.Close(ctx) 152 var rowToUpdate sql.Row 153 for rowToUpdate, err = rowIter.Next(ctx); err == nil; rowToUpdate, err = rowIter.Next(ctx) { 154 if depth > 15 { 155 return sql.ErrForeignKeyDepthLimit.New() 156 } 157 updatedRow := make(sql.Row, len(rowToUpdate)) 158 for i := range rowToUpdate { 159 mappedVal := refActionData.ChildParentMapping[i] 160 if mappedVal == -1 { 161 updatedRow[i] = rowToUpdate[i] 162 } else { 163 updatedRow[i] = new[mappedVal] 164 } 165 } 166 err = refActionData.Editor.Update(ctx, rowToUpdate, updatedRow, depth) 167 if err != nil { 168 return err 169 } 170 } 171 if err == io.EOF { 172 return nil 173 } 174 return err 175 } 176 177 // OnUpdateSetNull handles the ON UPDATE SET NULL referential action. 178 func (fkEditor *ForeignKeyEditor) OnUpdateSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row, depth int) error { 179 if ok, err := fkEditor.ColumnsUpdated(refActionData, old, new); err != nil { 180 return err 181 } else if !ok { 182 return nil 183 } 184 185 rowIter, err := refActionData.RowMapper.GetIter(ctx, old, false) 186 if err != nil { 187 return err 188 } 189 defer rowIter.Close(ctx) 190 var rowToUpdate sql.Row 191 for rowToUpdate, err = rowIter.Next(ctx); err == nil; rowToUpdate, err = rowIter.Next(ctx) { 192 if depth > 15 { 193 return sql.ErrForeignKeyDepthLimit.New() 194 } 195 updatedRow := make(sql.Row, len(rowToUpdate)) 196 for i := range rowToUpdate { 197 // Row contents are nil by default, so we only need to assign the non-affected values 198 if refActionData.ChildParentMapping[i] == -1 { 199 updatedRow[i] = rowToUpdate[i] 200 } 201 } 202 err = refActionData.Editor.Update(ctx, rowToUpdate, updatedRow, depth) 203 if err != nil { 204 return err 205 } 206 } 207 if err == io.EOF { 208 return nil 209 } 210 return err 211 } 212 213 // Delete handles both the standard DELETE statement and propagated referential actions from a parent table's ON DELETE. 214 func (fkEditor *ForeignKeyEditor) Delete(ctx *sql.Context, row sql.Row, depth int) error { 215 //TODO: may need to process some cascades after the update to avoid recursive violations, write some tests on this 216 for _, refActionData := range fkEditor.RefActions { 217 switch refActionData.ForeignKey.OnDelete { 218 default: // RESTRICT and friends 219 if err := fkEditor.OnDeleteRestrict(ctx, refActionData, row); err != nil { 220 return err 221 } 222 case sql.ForeignKeyReferentialAction_Cascade: 223 case sql.ForeignKeyReferentialAction_SetNull: 224 } 225 } 226 if err := fkEditor.Editor.Delete(ctx, row); err != nil { 227 return err 228 } 229 for _, refActionData := range fkEditor.RefActions { 230 switch refActionData.ForeignKey.OnDelete { 231 case sql.ForeignKeyReferentialAction_Cascade: 232 if err := fkEditor.OnDeleteCascade(ctx, refActionData, row, depth+1); err != nil { 233 return err 234 } 235 case sql.ForeignKeyReferentialAction_SetNull: 236 if err := fkEditor.OnDeleteSetNull(ctx, refActionData, row, depth+1); err != nil { 237 return err 238 } 239 } 240 } 241 return nil 242 } 243 244 // OnDeleteRestrict handles the ON DELETE RESTRICT referential action. 245 func (fkEditor *ForeignKeyEditor) OnDeleteRestrict(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row) error { 246 rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false) 247 if err != nil { 248 return err 249 } 250 defer rowIter.Close(ctx) 251 if _, err = rowIter.Next(ctx); err == nil { 252 return sql.ErrForeignKeyParentViolation.New(refActionData.ForeignKey.Name, 253 refActionData.ForeignKey.Table, refActionData.ForeignKey.ParentTable, refActionData.RowMapper.GetKeyString(row)) 254 } 255 if err != io.EOF { 256 return err 257 } 258 return nil 259 } 260 261 // OnDeleteCascade handles the ON DELETE CASCADE referential action. 262 func (fkEditor *ForeignKeyEditor) OnDeleteCascade(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error { 263 rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false) 264 if err != nil { 265 return err 266 } 267 defer rowIter.Close(ctx) 268 var rowToDelete sql.Row 269 for rowToDelete, err = rowIter.Next(ctx); err == nil; rowToDelete, err = rowIter.Next(ctx) { 270 // MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16. 271 // This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior. 272 if depth >= 15 { 273 if fkEditor.Cyclical { 274 return sql.ErrForeignKeyDepthLimit.New() 275 } else if depth > 15 { 276 return sql.ErrForeignKeyDepthLimit.New() 277 } 278 } 279 err = refActionData.Editor.Delete(ctx, rowToDelete, depth) 280 if err != nil { 281 return err 282 } 283 } 284 if err == io.EOF { 285 return nil 286 } 287 return err 288 } 289 290 // OnDeleteSetNull handles the ON DELETE SET NULL referential action. 291 func (fkEditor *ForeignKeyEditor) OnDeleteSetNull(ctx *sql.Context, refActionData ForeignKeyRefActionData, row sql.Row, depth int) error { 292 rowIter, err := refActionData.RowMapper.GetIter(ctx, row, false) 293 if err != nil { 294 return err 295 } 296 defer rowIter.Close(ctx) 297 var rowToNull sql.Row 298 for rowToNull, err = rowIter.Next(ctx); err == nil; rowToNull, err = rowIter.Next(ctx) { 299 // MySQL seems to have a bug where cyclical foreign keys return an error at a depth of 15 instead of 16. 300 // This replicates the observed behavior, regardless of whether we're replicating a bug or intentional behavior. 301 if depth >= 15 { 302 if fkEditor.Cyclical { 303 return sql.ErrForeignKeyDepthLimit.New() 304 } else if depth > 15 { 305 return sql.ErrForeignKeyDepthLimit.New() 306 } 307 } 308 nulledRow := make(sql.Row, len(rowToNull)) 309 for i := range rowToNull { 310 // Row contents are nil by default, so we only need to assign the non-affected values 311 if refActionData.ChildParentMapping[i] == -1 { 312 nulledRow[i] = rowToNull[i] 313 } 314 } 315 err = refActionData.Editor.Update(ctx, rowToNull, nulledRow, depth) 316 if err != nil { 317 return err 318 } 319 } 320 if err == io.EOF { 321 return nil 322 } 323 return err 324 } 325 326 // ColumnsUpdated returns whether the columns involved in the foreign key were updated. Some updates may only update 327 // columns that are not involved in a foreign key, and therefore we should ignore a CASCADE or SET NULL referential 328 // action in such cases. 329 func (fkEditor *ForeignKeyEditor) ColumnsUpdated(refActionData ForeignKeyRefActionData, old sql.Row, new sql.Row) (bool, error) { 330 for _, mappedVal := range refActionData.ChildParentMapping { 331 if mappedVal == -1 { 332 continue 333 } 334 oldVal := old[mappedVal] 335 newVal := new[mappedVal] 336 cmp, err := fkEditor.Schema[mappedVal].Type.Compare(oldVal, newVal) 337 if err != nil { 338 return false, err 339 } 340 if cmp != 0 { 341 return true, nil 342 } 343 } 344 return false, nil 345 } 346 347 // Close closes this handler along with all child handlers. 348 func (fkEditor *ForeignKeyEditor) Close(ctx *sql.Context) error { 349 err := fkEditor.Editor.Close(ctx) 350 for _, child := range fkEditor.RefActions { 351 nErr := child.Editor.Close(ctx) 352 if err == nil { 353 err = nErr 354 } 355 } 356 return err 357 } 358 359 // ForeignKeyReferenceHandler handles references to any parent rows to verify they exist. 360 type ForeignKeyReferenceHandler struct { 361 ForeignKey sql.ForeignKeyConstraint 362 RowMapper ForeignKeyRowMapper 363 SelfCols map[string]int // SelfCols are used for self-referential fks to refer to a col position given a col name 364 } 365 366 // IsInitialized returns whether this reference handler has been initialized. 367 func (reference *ForeignKeyReferenceHandler) IsInitialized() bool { 368 return reference.RowMapper.IsInitialized() 369 } 370 371 // CheckReference checks that the given row has an index entry in the referenced table. 372 func (reference *ForeignKeyReferenceHandler) CheckReference(ctx *sql.Context, row sql.Row) error { 373 // If even one of the values are NULL then we don't check the parent 374 for _, pos := range reference.RowMapper.IndexPositions { 375 if row[pos] == nil { 376 return nil 377 } 378 } 379 380 rowIter, err := reference.RowMapper.GetIter(ctx, row, true) 381 if err != nil { 382 return err 383 } 384 defer rowIter.Close(ctx) 385 386 _, err = rowIter.Next(ctx) 387 if err != nil && err != io.EOF { 388 return err 389 } 390 if err == nil { 391 // We have a parent row so throw no error 392 return nil 393 } 394 395 if reference.ForeignKey.IsSelfReferential() { 396 allMatch := true 397 for i := range reference.ForeignKey.Columns { 398 colPos := reference.SelfCols[reference.ForeignKey.Columns[i]] 399 refPos := reference.SelfCols[reference.ForeignKey.ParentColumns[i]] 400 cmp, err := reference.RowMapper.SourceSch[colPos].Type.Compare(row[colPos], row[refPos]) 401 if err != nil { 402 return err 403 } 404 if cmp != 0 { 405 allMatch = false 406 break 407 } 408 } 409 if allMatch { 410 return nil 411 } 412 } 413 return sql.ErrForeignKeyChildViolation.New(reference.ForeignKey.Name, reference.ForeignKey.Table, 414 reference.ForeignKey.ParentTable, reference.RowMapper.GetKeyString(row)) 415 } 416 417 // CheckTable checks that every row in the table has an index entry in the referenced table. 418 func (reference *ForeignKeyReferenceHandler) CheckTable(ctx *sql.Context, tbl sql.ForeignKeyTable) error { 419 partIter, err := tbl.Partitions(ctx) 420 if err != nil { 421 return err 422 } 423 rowIter := sql.NewTableRowIter(ctx, tbl, partIter) 424 defer rowIter.Close(ctx) 425 for row, err := rowIter.Next(ctx); err == nil; row, err = rowIter.Next(ctx) { 426 err = reference.CheckReference(ctx, row) 427 if err != nil { 428 return err 429 } 430 } 431 if err != io.EOF { 432 return err 433 } 434 return nil 435 } 436 437 // ForeignKeyRowMapper takes a source row and returns all matching rows on the contained table according to the row 438 // mapping from the source columns to the contained index's columns. 439 type ForeignKeyRowMapper struct { 440 Index sql.Index 441 Updater sql.ForeignKeyEditor 442 SourceSch sql.Schema 443 // IndexPositions hold the mapping between an index's column position and the source row's column position. Given 444 // an index (x1, x2) and a source row (y1, y2, y3) and the relation (x1->y3, x2->y1), this slice would contain 445 // [2, 0]. The first index column "x1" maps to the third source column "y3" (so position 2 since it's zero-based), 446 // and the second index column "x2" maps to the first source column "y1" (position 0). 447 IndexPositions []int 448 // AppendTypes hold any types that may be needed to complete an index range's generation. Foreign keys are allowed 449 // to use an index's prefix, and indexes expect ranges to reference all of their columns (not just the prefix), so 450 // we grab the types of the suffix index columns to append to the range after the prefix columns that we're 451 // referencing. 452 AppendTypes []sql.Type 453 } 454 455 // IsInitialized returns whether this mapper has been initialized. 456 func (mapper *ForeignKeyRowMapper) IsInitialized() bool { 457 return mapper.Updater != nil && mapper.Index != nil 458 } 459 460 // GetIter returns a row iterator for all rows that match the given source row. 461 func (mapper *ForeignKeyRowMapper) GetIter(ctx *sql.Context, row sql.Row, refCheck bool) (sql.RowIter, error) { 462 rang := make(sql.Range, len(mapper.IndexPositions)+len(mapper.AppendTypes)) 463 for rangPosition, rowPos := range mapper.IndexPositions { 464 rowVal := row[rowPos] 465 // If any value is NULL then it is ignored by foreign keys 466 if rowVal == nil { 467 return sql.RowsToRowIter(), nil 468 } 469 rang[rangPosition] = sql.ClosedRangeColumnExpr(rowVal, rowVal, mapper.SourceSch[rowPos].Type) 470 } 471 for i, appendType := range mapper.AppendTypes { 472 rang[i+len(mapper.IndexPositions)] = sql.AllRangeColumnExpr(appendType) 473 } 474 475 if !mapper.Index.CanSupport(rang) { 476 return nil, ErrInvalidLookupForIndexedTable.New(rang.DebugString()) 477 } 478 //TODO: profile this, may need to redesign this or add a fast path 479 lookup := sql.IndexLookup{Ranges: []sql.Range{rang}, Index: mapper.Index} 480 481 editorData := mapper.Updater.IndexedAccess(lookup) 482 483 if rc, ok := editorData.(sql.ReferenceChecker); refCheck && ok { 484 err := rc.SetReferenceCheck() 485 if err != nil { 486 return nil, err 487 } 488 } 489 490 partIter, err := editorData.LookupPartitions(ctx, lookup) 491 if err != nil { 492 return nil, err 493 } 494 return sql.NewTableRowIter(ctx, editorData, partIter), nil 495 } 496 497 // GetKeyString returns a string representing the key used to access the index. 498 func (mapper *ForeignKeyRowMapper) GetKeyString(row sql.Row) string { 499 keyStrParts := make([]string, len(mapper.IndexPositions)) 500 for i, rowPos := range mapper.IndexPositions { 501 keyStrParts[i] = fmt.Sprint(row[rowPos]) 502 } 503 return fmt.Sprintf("[%s]", strings.Join(keyStrParts, ",")) 504 } 505 506 // GetChildParentMapping returns a mapping from the foreign key columns of a child schema to the parent schema. 507 func GetChildParentMapping(parentSch sql.Schema, childSch sql.Schema, fkDef sql.ForeignKeyConstraint) (ChildParentMapping, error) { 508 parentMap := make(map[string]int) 509 for i, col := range parentSch { 510 parentMap[strings.ToLower(col.Name)] = i 511 } 512 childMap := make(map[string]int) 513 for i, col := range childSch { 514 childMap[strings.ToLower(col.Name)] = i 515 } 516 mapping := make(ChildParentMapping, len(childSch)) 517 for i := range mapping { 518 mapping[i] = -1 519 } 520 for i := range fkDef.Columns { 521 childIndex, ok := childMap[strings.ToLower(fkDef.Columns[i])] 522 if !ok { 523 return nil, fmt.Errorf("foreign key `%s` refers to column `%s` on table `%s` but it could not be found", 524 fkDef.Name, fkDef.Columns[i], fkDef.Table) 525 } 526 parentIndex, ok := parentMap[strings.ToLower(fkDef.ParentColumns[i])] 527 if !ok { 528 return nil, fmt.Errorf("foreign key `%s` refers to column `%s` on referenced table `%s` but it could not be found", 529 fkDef.Name, fkDef.ParentColumns[i], fkDef.ParentTable) 530 } 531 mapping[childIndex] = parentIndex 532 } 533 return mapping, nil 534 }