github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/foreign_key_coll.go (about) 1 // Copyright 2020 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 doltdb 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/binary" 21 "errors" 22 "fmt" 23 "sort" 24 "strings" 25 26 "github.com/dolthub/dolt/go/libraries/doltcore/row" 27 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 28 "github.com/dolthub/dolt/go/libraries/utils/set" 29 "github.com/dolthub/dolt/go/store/hash" 30 "github.com/dolthub/dolt/go/store/types" 31 32 "github.com/dolthub/go-mysql-server/sql" 33 ) 34 35 // ForeignKeyCollection represents the collection of foreign keys for a root value. 36 type ForeignKeyCollection struct { 37 foreignKeys map[string]ForeignKey 38 } 39 40 // ForeignKeyViolationError represents a set of foreign key violations for a table. 41 type ForeignKeyViolationError struct { 42 ForeignKey ForeignKey 43 Schema schema.Schema 44 ViolationRows []row.Row 45 } 46 47 // Error implements the interface error. 48 func (f *ForeignKeyViolationError) Error() string { 49 if len(f.ViolationRows) == 0 { 50 return "no violations were found, should not be an error" 51 } 52 sb := strings.Builder{} 53 const earlyTerminationLimit = 50 54 terminatedEarly := false 55 for i := range f.ViolationRows { 56 if i >= earlyTerminationLimit { 57 terminatedEarly = true 58 break 59 } 60 key, _ := f.ViolationRows[i].NomsMapKey(f.Schema).Value(context.Background()) 61 val, _ := f.ViolationRows[i].NomsMapValue(f.Schema).Value(context.Background()) 62 valSlice, _ := val.(types.Tuple).AsSlice() 63 all, _ := key.(types.Tuple).Append(valSlice...) 64 str, _ := types.EncodedValue(context.Background(), all) 65 sb.WriteRune('\n') 66 sb.WriteString(str) 67 } 68 if terminatedEarly { 69 return fmt.Sprintf("foreign key violations on `%s`.`%s`:%s\n%d more violations are not being displayed", 70 f.ForeignKey.Name, f.ForeignKey.TableName, sb.String(), len(f.ViolationRows)-earlyTerminationLimit) 71 } else { 72 return fmt.Sprintf("foreign key violations on `%s`.`%s`:%s", f.ForeignKey.Name, f.ForeignKey.TableName, sb.String()) 73 } 74 } 75 76 var _ error = (*ForeignKeyViolationError)(nil) 77 78 type ForeignKeyReferentialAction byte 79 80 const ( 81 ForeignKeyReferentialAction_DefaultAction ForeignKeyReferentialAction = iota 82 ForeignKeyReferentialAction_Cascade 83 ForeignKeyReferentialAction_NoAction 84 ForeignKeyReferentialAction_Restrict 85 ForeignKeyReferentialAction_SetNull 86 ) 87 88 // ForeignKey is the complete, internal representation of a Foreign Key. 89 type ForeignKey struct { 90 Name string `noms:"name" json:"name"` 91 TableName string `noms:"tbl_name" json:"tbl_name"` 92 TableIndex string `noms:"tbl_index" json:"tbl_index"` 93 TableColumns []uint64 `noms:"tbl_cols" json:"tbl_cols"` 94 ReferencedTableName string `noms:"ref_tbl_name" json:"ref_tbl_name"` 95 ReferencedTableIndex string `noms:"ref_tbl_index" json:"ref_tbl_index"` 96 ReferencedTableColumns []uint64 `noms:"ref_tbl_cols" json:"ref_tbl_cols"` 97 OnUpdate ForeignKeyReferentialAction `noms:"on_update" json:"on_update"` 98 OnDelete ForeignKeyReferentialAction `noms:"on_delete" json:"on_delete"` 99 UnresolvedFKDetails UnresolvedFKDetails `noms:"unres_fk,omitempty" json:"unres_fk,omitempty"` 100 } 101 102 // UnresolvedFKDetails contains any details necessary for an unresolved foreign key to resolve to a valid foreign key. 103 type UnresolvedFKDetails struct { 104 TableColumns []string `noms:"x_tbl_cols" json:"x_tbl_cols"` 105 ReferencedTableColumns []string `noms:"x_ref_tbl_cols" json:"x_ref_tbl_cols"` 106 } 107 108 // EqualDefs returns whether two foreign keys have the same definition over the same column sets. 109 // It does not compare table names or foreign key names. 110 func (fk ForeignKey) EqualDefs(other ForeignKey) bool { 111 if len(fk.TableColumns) != len(other.TableColumns) || len(fk.ReferencedTableColumns) != len(other.ReferencedTableColumns) { 112 return false 113 } 114 for i := range fk.TableColumns { 115 if fk.TableColumns[i] != other.TableColumns[i] { 116 return false 117 } 118 } 119 for i := range fk.ReferencedTableColumns { 120 if fk.ReferencedTableColumns[i] != other.ReferencedTableColumns[i] { 121 return false 122 } 123 } 124 return fk.Name == other.Name && 125 fk.OnUpdate == other.OnUpdate && 126 fk.OnDelete == other.OnDelete 127 } 128 129 // Equals compares this ForeignKey to |other| and returns true if they are equal. Foreign keys can either be in 130 // a "resolved" state, where the referenced columns in the parent and child tables are identified by column tags, 131 // or in an "unresolved" state where the reference columns in the parent and child are still identified by strings. 132 // If one foreign key is resolved and one is unresolved, the logic for comparing them requires resolving the string 133 // column names to column tags, which is why |fkSchemasByName| and |otherSchemasByName| are passed in. Each of these 134 // is a map of table schemas for |fk| and |other|, where the child table and every parent table referenced in the 135 // foreign key is present in the map. 136 func (fk ForeignKey) Equals(other ForeignKey, fkSchemasByName, otherSchemasByName map[string]schema.Schema) bool { 137 // If both FKs are resolved or unresolved, we can just deeply compare them 138 if fk.IsResolved() == other.IsResolved() { 139 return fk.DeepEquals(other) 140 } 141 142 // Otherwise, one FK is resolved and one is not, so we need to work a little harder 143 // to calculate equality since their referenced columns are represented differently. 144 // First check the attributes that don't change when an FK is resolved or unresolved. 145 if fk.Name != other.Name && 146 fk.TableName != other.TableName && 147 fk.ReferencedTableName != other.ReferencedTableName && 148 fk.TableIndex != other.TableIndex && 149 fk.ReferencedTableIndex != other.ReferencedTableIndex && 150 fk.OnUpdate == other.OnUpdate && 151 fk.OnDelete == other.OnDelete { 152 return false 153 } 154 155 // Sort out which FK is resolved and which is not 156 var resolvedFK, unresolvedFK ForeignKey 157 var resolvedSchemasByName map[string]schema.Schema 158 if fk.IsResolved() { 159 resolvedFK, unresolvedFK, resolvedSchemasByName = fk, other, fkSchemasByName 160 } else { 161 resolvedFK, unresolvedFK, resolvedSchemasByName = other, fk, otherSchemasByName 162 } 163 164 // Check the columns on the child table 165 if len(resolvedFK.TableColumns) != len(unresolvedFK.UnresolvedFKDetails.TableColumns) { 166 return false 167 } 168 for i, tag := range resolvedFK.TableColumns { 169 unresolvedColName := unresolvedFK.UnresolvedFKDetails.TableColumns[i] 170 resolvedSch, ok := resolvedSchemasByName[resolvedFK.TableName] 171 if !ok { 172 return false 173 } 174 resolvedCol, ok := resolvedSch.GetAllCols().GetByTag(tag) 175 if !ok { 176 return false 177 } 178 if resolvedCol.Name != unresolvedColName { 179 return false 180 } 181 } 182 183 // Check the columns on the parent table 184 if len(resolvedFK.ReferencedTableColumns) != len(unresolvedFK.UnresolvedFKDetails.ReferencedTableColumns) { 185 return false 186 } 187 for i, tag := range resolvedFK.ReferencedTableColumns { 188 unresolvedColName := unresolvedFK.UnresolvedFKDetails.ReferencedTableColumns[i] 189 resolvedSch, ok := resolvedSchemasByName[unresolvedFK.ReferencedTableName] 190 if !ok { 191 return false 192 } 193 resolvedCol, ok := resolvedSch.GetAllCols().GetByTag(tag) 194 if !ok { 195 return false 196 } 197 if resolvedCol.Name != unresolvedColName { 198 return false 199 } 200 } 201 202 return true 203 } 204 205 // DeepEquals compares all attributes of a foreign key to another, including name and 206 // table names. Note that if one foreign key is resolved and the other is NOT resolved, 207 // then this function will not calculate equality correctly. When comparing a resolved 208 // FK with an unresolved FK, the ForeignKey.Equals() function should be used instead. 209 func (fk ForeignKey) DeepEquals(other ForeignKey) bool { 210 if !fk.EqualDefs(other) { 211 return false 212 } 213 return fk.Name == other.Name && 214 fk.TableName == other.TableName && 215 fk.ReferencedTableName == other.ReferencedTableName && 216 fk.TableIndex == other.TableIndex && 217 fk.ReferencedTableIndex == other.ReferencedTableIndex 218 } 219 220 // HashOf returns the Noms hash of a ForeignKey. 221 func (fk ForeignKey) HashOf() (hash.Hash, error) { 222 var fields []interface{} 223 fields = append(fields, fk.Name, fk.TableName, fk.TableIndex) 224 for _, t := range fk.TableColumns { 225 fields = append(fields, t) 226 } 227 fields = append(fields, fk.ReferencedTableName, fk.ReferencedTableIndex) 228 for _, t := range fk.ReferencedTableColumns { 229 fields = append(fields, t) 230 } 231 fields = append(fields, []byte{byte(fk.OnUpdate), byte(fk.OnDelete)}) 232 for _, col := range fk.UnresolvedFKDetails.TableColumns { 233 fields = append(fields, col) 234 } 235 for _, col := range fk.UnresolvedFKDetails.ReferencedTableColumns { 236 fields = append(fields, col) 237 } 238 239 var bb bytes.Buffer 240 for _, field := range fields { 241 var err error 242 switch t := field.(type) { 243 case string: 244 _, err = bb.Write([]byte(t)) 245 case []byte: 246 _, err = bb.Write(t) 247 case uint64: 248 err = binary.Write(&bb, binary.LittleEndian, t) 249 default: 250 return hash.Hash{}, fmt.Errorf("unsupported type %T", t) 251 } 252 if err != nil { 253 return hash.Hash{}, err 254 } 255 } 256 257 return hash.Of(bb.Bytes()), nil 258 } 259 260 // CombinedHash returns a combined hash value for all foreign keys in the slice provided. 261 // An empty slice has a zero hash. 262 func CombinedHash(fks []ForeignKey) (hash.Hash, error) { 263 if len(fks) == 0 { 264 return hash.Hash{}, nil 265 } 266 267 var bb bytes.Buffer 268 for _, fk := range fks { 269 h, err := fk.HashOf() 270 if err != nil { 271 return hash.Hash{}, err 272 } 273 bb.Write(h[:]) 274 } 275 276 return hash.Of(bb.Bytes()), nil 277 } 278 279 // IsSelfReferential returns whether the table declaring the foreign key is also referenced by the foreign key. 280 func (fk ForeignKey) IsSelfReferential() bool { 281 return strings.ToLower(fk.TableName) == strings.ToLower(fk.ReferencedTableName) 282 } 283 284 // IsResolved returns whether the foreign key has been resolved. 285 func (fk ForeignKey) IsResolved() bool { 286 return len(fk.TableColumns) > 0 && len(fk.ReferencedTableColumns) > 0 287 } 288 289 // ValidateReferencedTableSchema verifies that the given schema matches the expectation of the referenced table. 290 func (fk ForeignKey) ValidateReferencedTableSchema(sch schema.Schema) error { 291 // An unresolved foreign key will be validated later, so we don't return an error here. 292 if !fk.IsResolved() { 293 return nil 294 } 295 allSchCols := sch.GetAllCols() 296 for _, colTag := range fk.ReferencedTableColumns { 297 _, ok := allSchCols.GetByTag(colTag) 298 if !ok { 299 return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` has unexpected schema", 300 fk.Name, fk.ReferencedTableName) 301 } 302 } 303 304 if (fk.ReferencedTableIndex != "" && !sch.Indexes().Contains(fk.ReferencedTableIndex)) || (fk.ReferencedTableIndex == "" && sch.GetPKCols().Size() < len(fk.ReferencedTableColumns)) { 305 return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` is missing the index `%s`", 306 fk.Name, fk.ReferencedTableName, fk.ReferencedTableIndex) 307 } 308 return nil 309 } 310 311 // ValidateTableSchema verifies that the given schema matches the expectation of the declaring table. 312 func (fk ForeignKey) ValidateTableSchema(sch schema.Schema) error { 313 // An unresolved foreign key will be validated later, so we don't return an error here. 314 if !fk.IsResolved() { 315 return nil 316 } 317 allSchCols := sch.GetAllCols() 318 for _, colTag := range fk.TableColumns { 319 _, ok := allSchCols.GetByTag(colTag) 320 if !ok { 321 return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` has unexpected schema", fk.Name, fk.TableName) 322 } 323 } 324 if (fk.TableIndex != "" && !sch.Indexes().Contains(fk.TableIndex)) || (fk.TableIndex == "" && sch.GetPKCols().Size() < len(fk.TableColumns)) { 325 return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` is missing the index `%s`", 326 fk.Name, fk.TableName, fk.TableIndex) 327 } 328 return nil 329 } 330 331 func NewForeignKeyCollection(keys ...ForeignKey) (*ForeignKeyCollection, error) { 332 fkc := &ForeignKeyCollection{ 333 foreignKeys: make(map[string]ForeignKey), 334 } 335 for _, k := range keys { 336 err := fkc.AddKeys(k) 337 if err != nil { 338 return nil, err 339 } 340 } 341 return fkc, nil 342 } 343 344 // AddKeys adds the given foreign key to the collection. Checks that the given name is unique in the collection, and that 345 // both column counts are equal. All other validation should occur before being added to the collection. 346 func (fkc *ForeignKeyCollection) AddKeys(fks ...ForeignKey) error { 347 for _, key := range fks { 348 if _, ok := fkc.GetByNameCaseInsensitive(key.Name); ok { 349 return sql.ErrForeignKeyDuplicateName.New(key.Name) 350 } 351 if len(key.TableColumns) != len(key.ReferencedTableColumns) { 352 return fmt.Errorf("foreign keys must have the same number of columns declared and referenced") 353 } 354 355 fkHash, err := key.HashOf() 356 if err != nil { 357 return err 358 } 359 fkc.foreignKeys[fkHash.String()] = key 360 } 361 return nil 362 } 363 364 // AllKeys returns a slice, sorted by name ascending, containing all of the foreign keys in this collection. 365 func (fkc *ForeignKeyCollection) AllKeys() []ForeignKey { 366 fks := make([]ForeignKey, len(fkc.foreignKeys)) 367 i := 0 368 for _, fk := range fkc.foreignKeys { 369 fks[i] = fk 370 i++ 371 } 372 sort.Slice(fks, func(i, j int) bool { 373 return fks[i].Name < fks[j].Name 374 }) 375 return fks 376 } 377 378 // Contains returns whether the given foreign key name already exists for this collection. 379 func (fkc *ForeignKeyCollection) Contains(foreignKeyName string) bool { 380 _, ok := fkc.GetByNameCaseInsensitive(foreignKeyName) 381 return ok 382 } 383 384 // HashOf returns the hash of the serialized form of this foreign key collection 385 func (fkc *ForeignKeyCollection) HashOf(ctx context.Context, vrw types.ValueReadWriter) (hash.Hash, error) { 386 serialized, err := SerializeForeignKeys(ctx, vrw, fkc) 387 if err != nil { 388 return hash.Hash{}, err 389 } 390 391 return serialized.Hash(vrw.Format()) 392 } 393 394 // Count returns the number of indexes in this collection. 395 func (fkc *ForeignKeyCollection) Count() int { 396 return len(fkc.foreignKeys) 397 } 398 399 // GetByNameCaseInsensitive returns a ForeignKey with a matching case-insensitive name, and whether a match exists. 400 func (fkc *ForeignKeyCollection) GetByNameCaseInsensitive(foreignKeyName string) (ForeignKey, bool) { 401 if foreignKeyName == "" { 402 return ForeignKey{}, false 403 } 404 for _, fk := range fkc.foreignKeys { 405 if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) { 406 return fk, true 407 } 408 } 409 return ForeignKey{}, false 410 } 411 412 type FkIndexUpdate struct { 413 FkName string 414 FromIdx string 415 ToIdx string 416 } 417 418 // UpdateIndexes updates the indexes used by the foreign keys as outlined by the update structs given. All foreign 419 // keys updated in this manner must belong to the same table, whose schema is provided. 420 func (fkc *ForeignKeyCollection) UpdateIndexes(ctx context.Context, tableSchema schema.Schema, updates []FkIndexUpdate) error { 421 for _, u := range updates { 422 fk, ok := fkc.GetByNameCaseInsensitive(u.FkName) 423 if !ok { 424 return errors.New("foreign key not found") 425 } 426 fkc.RemoveKeys(fk) 427 fk.ReferencedTableIndex = u.ToIdx 428 429 err := fkc.AddKeys(fk) 430 if err != nil { 431 return err 432 } 433 434 err = fk.ValidateReferencedTableSchema(tableSchema) 435 if err != nil { 436 return err 437 } 438 } 439 440 return nil 441 } 442 443 // GetByTags gets the ForeignKey defined over the parent and child columns corresponding to their tags. 444 func (fkc *ForeignKeyCollection) GetByTags(childTags, parentTags []uint64) (ForeignKey, bool) { 445 if len(childTags) == 0 || len(parentTags) == 0 { 446 return ForeignKey{}, false 447 } 448 OuterLoop: 449 for _, fk := range fkc.foreignKeys { 450 if len(fk.ReferencedTableColumns) != len(parentTags) { 451 continue 452 } 453 for i, t := range fk.ReferencedTableColumns { 454 if t != parentTags[i] { 455 continue OuterLoop 456 } 457 } 458 459 if len(fk.TableColumns) != len(childTags) { 460 continue 461 } 462 for i, t := range fk.TableColumns { 463 if t != childTags[i] { 464 continue OuterLoop 465 } 466 } 467 return fk, true 468 } 469 return ForeignKey{}, false 470 } 471 472 // GetMatchingKey gets the ForeignKey defined over the parent and child columns. If the given foreign key is resolved, 473 // then both resolved and unresolved keys are checked for a match. If the given foreign key is unresolved, then ONLY 474 // unresolved keys may be found. 475 // 476 // This discrepancy is due to the primary uses for this function. It is assumed that the ForeignKeyCollection is an 477 // ancestor collection compared to the collection that the given key comes from. Therefore, the key found in the 478 // ancestor will usually be the unresolved version of the given key, hence the comparison is valid. However, if the 479 // given key is unresolved, it is treated as a new key, which cannot be matched to a resolved key that previously 480 // existed. 481 // 482 // The given schema map is keyed by table name, and is used in the event that the given key is resolved and any keys in 483 // the collection are unresolved. A "dirty resolution" is performed, which matches the column names to tags, and then a 484 // standard tag comparison is performed. If a table or column is not in the map, then the foreign key is ignored. 485 func (fkc *ForeignKeyCollection) GetMatchingKey(fk ForeignKey, allSchemas map[string]schema.Schema) (ForeignKey, bool) { 486 if !fk.IsResolved() { 487 // The given foreign key is unresolved, so we only look for matches on unresolved keys 488 OuterLoopUnresolved: 489 for _, existingFk := range fkc.foreignKeys { 490 // For unresolved keys, the table name is important (column tags are globally unique, column names are not) 491 if existingFk.IsResolved() || 492 fk.TableName != existingFk.TableName || 493 fk.ReferencedTableName != existingFk.ReferencedTableName || 494 len(fk.UnresolvedFKDetails.TableColumns) != len(existingFk.UnresolvedFKDetails.TableColumns) || 495 len(fk.UnresolvedFKDetails.ReferencedTableColumns) != len(existingFk.UnresolvedFKDetails.ReferencedTableColumns) { 496 continue 497 } 498 for i, fkCol := range fk.UnresolvedFKDetails.TableColumns { 499 if fkCol != existingFk.UnresolvedFKDetails.TableColumns[i] { 500 continue OuterLoopUnresolved 501 } 502 } 503 for i, fkCol := range fk.UnresolvedFKDetails.ReferencedTableColumns { 504 if fkCol != existingFk.UnresolvedFKDetails.ReferencedTableColumns[i] { 505 continue OuterLoopUnresolved 506 } 507 } 508 return existingFk, true 509 } 510 return ForeignKey{}, false 511 } 512 // The given foreign key is resolved, so we may match both resolved and unresolved keys 513 OuterLoopResolved: 514 for _, existingFk := range fkc.foreignKeys { 515 if existingFk.IsResolved() { 516 // When both are resolved, we do a standard tag comparison 517 if len(fk.TableColumns) != len(existingFk.TableColumns) || 518 len(fk.ReferencedTableColumns) != len(existingFk.ReferencedTableColumns) { 519 continue 520 } 521 for i, tag := range fk.TableColumns { 522 if tag != existingFk.TableColumns[i] { 523 continue OuterLoopResolved 524 } 525 } 526 for i, tag := range fk.ReferencedTableColumns { 527 if tag != existingFk.ReferencedTableColumns[i] { 528 continue OuterLoopResolved 529 } 530 } 531 return existingFk, true 532 } else { 533 // Since the existing key is unresolved, we reference the schema map to get tags we can use 534 if len(fk.TableColumns) != len(existingFk.UnresolvedFKDetails.TableColumns) || 535 len(fk.ReferencedTableColumns) != len(existingFk.UnresolvedFKDetails.ReferencedTableColumns) { 536 continue 537 } 538 tblSch, ok := allSchemas[existingFk.TableName] 539 if !ok { 540 continue 541 } 542 refTblSch, ok := allSchemas[existingFk.ReferencedTableName] 543 if !ok { 544 continue 545 } 546 for i, tag := range fk.TableColumns { 547 col, ok := tblSch.GetAllCols().GetByNameCaseInsensitive(existingFk.UnresolvedFKDetails.TableColumns[i]) 548 if !ok || tag != col.Tag { 549 continue OuterLoopResolved 550 } 551 } 552 for i, tag := range fk.ReferencedTableColumns { 553 col, ok := refTblSch.GetAllCols().GetByNameCaseInsensitive(existingFk.UnresolvedFKDetails.ReferencedTableColumns[i]) 554 if !ok || tag != col.Tag { 555 continue OuterLoopResolved 556 } 557 } 558 return existingFk, true 559 } 560 } 561 return ForeignKey{}, false 562 } 563 564 func (fkc *ForeignKeyCollection) Iter(cb func(fk ForeignKey) (stop bool, err error)) error { 565 for _, fk := range fkc.foreignKeys { 566 stop, err := cb(fk) 567 if err != nil { 568 return err 569 } 570 if stop { 571 return err 572 } 573 } 574 return nil 575 } 576 577 // KeysForTable returns all foreign keys that reference the given table in some capacity. The returned array 578 // declaredFk contains all foreign keys in which this table declared the foreign key. The array referencedByFk contains 579 // all foreign keys in which this table is the referenced table. If the table contains a self-referential foreign key, 580 // it will be present in both declaresFk and referencedByFk. Each array is sorted by name ascending. 581 func (fkc *ForeignKeyCollection) KeysForTable(tableName string) (declaredFk, referencedByFk []ForeignKey) { 582 lowercaseTblName := strings.ToLower(tableName) 583 for _, foreignKey := range fkc.foreignKeys { 584 if strings.ToLower(foreignKey.TableName) == lowercaseTblName { 585 declaredFk = append(declaredFk, foreignKey) 586 } 587 if strings.ToLower(foreignKey.ReferencedTableName) == lowercaseTblName { 588 referencedByFk = append(referencedByFk, foreignKey) 589 } 590 } 591 sort.Slice(declaredFk, func(i, j int) bool { 592 return declaredFk[i].Name < declaredFk[j].Name 593 }) 594 sort.Slice(referencedByFk, func(i, j int) bool { 595 return referencedByFk[i].Name < referencedByFk[j].Name 596 }) 597 return 598 } 599 600 // RemoveKeys removes any Foreign Keys with matching column set from the collection. 601 func (fkc *ForeignKeyCollection) RemoveKeys(fks ...ForeignKey) { 602 drops := set.NewStrSet(nil) 603 for _, outgoing := range fks { 604 for k, existing := range fkc.foreignKeys { 605 if outgoing.EqualDefs(existing) { 606 drops.Add(k) 607 } 608 } 609 } 610 for _, k := range drops.AsSlice() { 611 delete(fkc.foreignKeys, k) 612 } 613 } 614 615 // RemoveKeyByName removes a foreign key from the collection. It does not remove the associated indexes from their 616 // respective tables. Returns true if the key was successfully removed. 617 func (fkc *ForeignKeyCollection) RemoveKeyByName(foreignKeyName string) bool { 618 var key string 619 for k, fk := range fkc.foreignKeys { 620 if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) { 621 key = k 622 break 623 } 624 } 625 if key == "" { 626 return false 627 } 628 delete(fkc.foreignKeys, key) 629 return true 630 } 631 632 // RemoveTables removes all foreign keys associated with the given tables, if permitted. The operation assumes that ALL 633 // tables to be removed are in a single call, as splitting tables into different calls may result in unintended errors. 634 func (fkc *ForeignKeyCollection) RemoveTables(ctx context.Context, tables ...string) error { 635 outgoing := set.NewStrSet(tables) 636 for _, fk := range fkc.foreignKeys { 637 dropChild := outgoing.Contains(fk.TableName) 638 dropParent := outgoing.Contains(fk.ReferencedTableName) 639 if dropParent && !dropChild { 640 return fmt.Errorf("unable to remove `%s` since it is referenced from table `%s`", fk.ReferencedTableName, fk.TableName) 641 } 642 if dropChild { 643 fkHash, err := fk.HashOf() 644 if err != nil { 645 return err 646 } 647 delete(fkc.foreignKeys, fkHash.String()) 648 } 649 } 650 return nil 651 } 652 653 // RemoveAndUnresolveTables removes all foreign keys associated with the given tables. If a parent is dropped without 654 // its child, then the foreign key goes to an unresolved state. The operation assumes that ALL tables to be removed are 655 // in a single call, as splitting tables into different calls may result in unintended errors. 656 func (fkc *ForeignKeyCollection) RemoveAndUnresolveTables(ctx context.Context, root RootValue, tables ...string) error { 657 outgoing := set.NewStrSet(tables) 658 for _, fk := range fkc.foreignKeys { 659 dropChild := outgoing.Contains(fk.TableName) 660 dropParent := outgoing.Contains(fk.ReferencedTableName) 661 if dropParent && !dropChild { 662 if !fk.IsResolved() { 663 continue 664 } 665 666 fkHash, err := fk.HashOf() 667 if err != nil { 668 return err 669 } 670 delete(fkc.foreignKeys, fkHash.String()) 671 672 fk.UnresolvedFKDetails.TableColumns = make([]string, len(fk.TableColumns)) 673 fk.UnresolvedFKDetails.ReferencedTableColumns = make([]string, len(fk.ReferencedTableColumns)) 674 675 tbl, ok, err := root.GetTable(ctx, TableName{Name: fk.TableName}) 676 if err != nil { 677 return err 678 } 679 if !ok { 680 return fmt.Errorf("table `%s` declares the resolved foreign key `%s` but the table cannot be found", 681 fk.TableName, fk.Name) 682 } 683 sch, err := tbl.GetSchema(ctx) 684 if err != nil { 685 return err 686 } 687 for i, tag := range fk.TableColumns { 688 col, ok := sch.GetAllCols().GetByTag(tag) 689 if !ok { 690 return fmt.Errorf("table `%s` uses tag `%d` in foreign key `%s` but no matching column was found", 691 fk.TableName, tag, fk.Name) 692 } 693 fk.UnresolvedFKDetails.TableColumns[i] = col.Name 694 } 695 696 refTbl, ok, err := root.GetTable(ctx, TableName{Name: fk.ReferencedTableName}) 697 if err != nil { 698 return err 699 } 700 if !ok { 701 return fmt.Errorf("table `%s` is referenced by the resolved foreign key `%s` but cannot be found", 702 fk.ReferencedTableName, fk.Name) 703 } 704 refSch, err := refTbl.GetSchema(ctx) 705 if err != nil { 706 return err 707 } 708 for i, tag := range fk.ReferencedTableColumns { 709 col, ok := refSch.GetAllCols().GetByTag(tag) 710 if !ok { 711 return fmt.Errorf("table `%s` uses tag `%d` in foreign key `%s` but no matching column was found", 712 fk.ReferencedTableName, tag, fk.Name) 713 } 714 fk.UnresolvedFKDetails.ReferencedTableColumns[i] = col.Name 715 } 716 717 fk.TableColumns = nil 718 fk.ReferencedTableColumns = nil 719 fk.TableIndex = "" 720 fk.ReferencedTableIndex = "" 721 722 fkHash, err = fk.HashOf() 723 if err != nil { 724 return err 725 } 726 fkc.foreignKeys[fkHash.String()] = fk 727 } 728 if dropChild { 729 fkHash, err := fk.HashOf() 730 if err != nil { 731 return err 732 } 733 delete(fkc.foreignKeys, fkHash.String()) 734 } 735 } 736 return nil 737 } 738 739 // Tables returns the set of all tables that either declare a foreign key or are referenced by a foreign key. 740 func (fkc *ForeignKeyCollection) Tables() map[string]struct{} { 741 tables := make(map[string]struct{}) 742 for _, fk := range fkc.foreignKeys { 743 tables[fk.TableName] = struct{}{} 744 tables[fk.ReferencedTableName] = struct{}{} 745 } 746 return tables 747 } 748 749 // String returns the SQL reference option in uppercase. 750 func (refOp ForeignKeyReferentialAction) String() string { 751 switch refOp { 752 case ForeignKeyReferentialAction_DefaultAction: 753 return "NONE SPECIFIED" 754 case ForeignKeyReferentialAction_Cascade: 755 return "CASCADE" 756 case ForeignKeyReferentialAction_NoAction: 757 return "NO ACTION" 758 case ForeignKeyReferentialAction_Restrict: 759 return "RESTRICT" 760 case ForeignKeyReferentialAction_SetNull: 761 return "SET NULL" 762 default: 763 return "INVALID" 764 } 765 } 766 767 // ReducedString returns the SQL reference option in uppercase. All reference options are functionally equivalent to 768 // either RESTRICT, CASCADE, or SET NULL, therefore only one those three options are returned. 769 func (refOp ForeignKeyReferentialAction) ReducedString() string { 770 switch refOp { 771 case ForeignKeyReferentialAction_DefaultAction, ForeignKeyReferentialAction_NoAction, ForeignKeyReferentialAction_Restrict: 772 return "RESTRICT" 773 case ForeignKeyReferentialAction_Cascade: 774 return "CASCADE" 775 case ForeignKeyReferentialAction_SetNull: 776 return "SET NULL" 777 default: 778 return "INVALID" 779 } 780 } 781 782 // ColumnHasFkRelationship returns a foreign key that uses this tag. Returns n 783 func (fkc *ForeignKeyCollection) ColumnHasFkRelationship(tag uint64) (ForeignKey, bool) { 784 for _, key := range fkc.AllKeys() { 785 tags := append(key.TableColumns, key.ReferencedTableColumns...) 786 787 for _, keyTag := range tags { 788 if tag == keyTag { 789 return key, true 790 } 791 } 792 } 793 794 return ForeignKey{}, false 795 } 796 797 // Copy returns an exact copy of the calling collection. As collections are meant to be modified in-place, this ensures 798 // that the original collection is not affected by any operations applied to the copied collection. 799 func (fkc *ForeignKeyCollection) Copy() *ForeignKeyCollection { 800 copiedForeignKeys := make(map[string]ForeignKey) 801 for hashOf, key := range fkc.foreignKeys { 802 copiedForeignKeys[hashOf] = key 803 } 804 return &ForeignKeyCollection{copiedForeignKeys} 805 }