github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/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 "fmt" 22 "io" 23 "sort" 24 "strings" 25 26 "github.com/dolthub/dolt/go/libraries/doltcore/row" 27 "github.com/dolthub/dolt/go/libraries/doltcore/rowconv" 28 "github.com/dolthub/dolt/go/libraries/doltcore/table/typed/noms" 29 30 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 31 "github.com/dolthub/dolt/go/libraries/utils/set" 32 "github.com/dolthub/dolt/go/store/hash" 33 "github.com/dolthub/dolt/go/store/marshal" 34 "github.com/dolthub/dolt/go/store/types" 35 ) 36 37 type ForeignKeyCollection struct { 38 foreignKeys map[string]ForeignKey 39 } 40 41 type ForeignKeyReferenceOption byte 42 43 const ( 44 ForeignKeyReferenceOption_DefaultAction ForeignKeyReferenceOption = iota 45 ForeignKeyReferenceOption_Cascade 46 ForeignKeyReferenceOption_NoAction 47 ForeignKeyReferenceOption_Restrict 48 ForeignKeyReferenceOption_SetNull 49 ) 50 51 // ForeignKey is the complete, internal representation of a Foreign Key. 52 type ForeignKey struct { 53 // TODO: remove index names and retrieve indexes by column tags 54 Name string `noms:"name" json:"name"` 55 TableName string `noms:"tbl_name" json:"tbl_name"` 56 TableIndex string `noms:"tbl_index" json:"tbl_index"` 57 TableColumns []uint64 `noms:"tbl_cols" json:"tbl_cols"` 58 ReferencedTableName string `noms:"ref_tbl_name" json:"ref_tbl_name"` 59 ReferencedTableIndex string `noms:"ref_tbl_index" json:"ref_tbl_index"` 60 ReferencedTableColumns []uint64 `noms:"ref_tbl_cols" json:"ref_tbl_cols"` 61 OnUpdate ForeignKeyReferenceOption `noms:"on_update" json:"on_update"` 62 OnDelete ForeignKeyReferenceOption `noms:"on_delete" json:"on_delete"` 63 } 64 65 // EqualDefs returns whether two foreign keys have the same definition over the same column sets. 66 // It does not compare table names or foreign key names. 67 func (fk ForeignKey) EqualDefs(other ForeignKey) bool { 68 if len(fk.TableColumns) != len(other.TableColumns) || len(fk.ReferencedTableColumns) != len(other.ReferencedTableColumns) { 69 return false 70 } 71 for i := range fk.TableColumns { 72 if fk.TableColumns[i] != other.TableColumns[i] { 73 return false 74 } 75 } 76 for i := range fk.ReferencedTableColumns { 77 if fk.ReferencedTableColumns[i] != other.ReferencedTableColumns[i] { 78 return false 79 } 80 } 81 return fk.Name == other.Name && 82 fk.OnUpdate == other.OnUpdate && 83 fk.OnDelete == other.OnDelete 84 } 85 86 // DeepEquals compares all attributes of a foreign key to another, including name and table names. 87 func (fk ForeignKey) DeepEquals(other ForeignKey) bool { 88 if !fk.EqualDefs(other) { 89 return false 90 } 91 return fk.Name == other.Name && 92 fk.TableName == other.TableName && 93 fk.ReferencedTableName == other.ReferencedTableName 94 } 95 96 // HashOf returns the Noms hash of a ForeignKey. 97 func (fk ForeignKey) HashOf() hash.Hash { 98 var bb bytes.Buffer 99 bb.Write([]byte(fk.Name)) 100 bb.Write([]byte(fk.TableName)) 101 bb.Write([]byte(fk.TableIndex)) 102 for _, t := range fk.TableColumns { 103 _ = binary.Write(&bb, binary.LittleEndian, t) 104 } 105 bb.Write([]byte(fk.ReferencedTableName)) 106 bb.Write([]byte(fk.ReferencedTableIndex)) 107 for _, t := range fk.ReferencedTableColumns { 108 _ = binary.Write(&bb, binary.LittleEndian, t) 109 } 110 bb.Write([]byte{byte(fk.OnUpdate), byte(fk.OnDelete)}) 111 112 return hash.Of(bb.Bytes()) 113 } 114 115 // IsSelfReferential returns whether the table declaring the foreign key is also referenced by the foreign key. 116 func (fk ForeignKey) IsSelfReferential() bool { 117 return strings.ToLower(fk.TableName) == strings.ToLower(fk.ReferencedTableName) 118 } 119 120 // ValidateReferencedTableSchema verifies that the given schema matches the expectation of the referenced table. 121 func (fk ForeignKey) ValidateReferencedTableSchema(sch schema.Schema) error { 122 allSchCols := sch.GetAllCols() 123 for _, colTag := range fk.ReferencedTableColumns { 124 _, ok := allSchCols.GetByTag(colTag) 125 if !ok { 126 return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` has unexpected schema", 127 fk.Name, fk.ReferencedTableName) 128 } 129 } 130 if !sch.Indexes().Contains(fk.ReferencedTableIndex) { 131 return fmt.Errorf("foreign key `%s` has entered an invalid state, referenced table `%s` is missing the index `%s`", 132 fk.Name, fk.ReferencedTableName, fk.ReferencedTableIndex) 133 } 134 return nil 135 } 136 137 // ValidateTableSchema verifies that the given schema matches the expectation of the declaring table. 138 func (fk ForeignKey) ValidateTableSchema(sch schema.Schema) error { 139 allSchCols := sch.GetAllCols() 140 for _, colTag := range fk.TableColumns { 141 _, ok := allSchCols.GetByTag(colTag) 142 if !ok { 143 return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` has unexpected schema", fk.Name, fk.TableName) 144 } 145 } 146 if !sch.Indexes().Contains(fk.TableIndex) { 147 return fmt.Errorf("foreign key `%s` has entered an invalid state, table `%s` is missing the index `%s`", 148 fk.Name, fk.TableName, fk.TableIndex) 149 } 150 return nil 151 } 152 153 // LoadForeignKeyCollection returns a new ForeignKeyCollection using the provided map returned previously by GetMap. 154 func LoadForeignKeyCollection(ctx context.Context, fkMap types.Map) (*ForeignKeyCollection, error) { 155 fkc := &ForeignKeyCollection{ 156 foreignKeys: make(map[string]ForeignKey), 157 } 158 err := fkMap.IterAll(ctx, func(key, value types.Value) error { 159 foreignKey := &ForeignKey{} 160 err := marshal.Unmarshal(ctx, fkMap.Format(), value, foreignKey) 161 if err != nil { 162 return err 163 } 164 fkc.foreignKeys[string(key.(types.String))] = *foreignKey 165 return nil 166 }) 167 if err != nil { 168 return nil, err 169 } 170 return fkc, nil 171 } 172 173 func NewForeignKeyCollection(keys ...ForeignKey) (*ForeignKeyCollection, error) { 174 fkc := &ForeignKeyCollection{ 175 foreignKeys: make(map[string]ForeignKey), 176 } 177 for _, k := range keys { 178 err := fkc.AddKeys(k) 179 if err != nil { 180 return nil, err 181 } 182 } 183 return fkc, nil 184 } 185 186 // AddKeys adds the given foreign key to the collection. Checks that the given name is unique in the collection, and that 187 // both column counts are equal. All other validation should occur before being added to the collection. 188 func (fkc *ForeignKeyCollection) AddKeys(fks ...ForeignKey) error { 189 for _, key := range fks { 190 if key.Name == "" { 191 // assign a name based on the hash 192 // 8 char = 5 base32 bytes, should be collision resistant 193 // TODO: constraint names should be unique, and this isn't guaranteed to be. 194 // This logic needs to live at the table / DB level. 195 key.Name = key.HashOf().String()[:8] 196 } 197 198 if _, ok := fkc.GetByTags(key.TableColumns, key.ReferencedTableColumns); ok { 199 // this differs from MySQL's logic 200 return fmt.Errorf("a foreign key over columns %v and referenced columns %v already exists", 201 key.TableColumns, key.ReferencedTableColumns) 202 } 203 if _, ok := fkc.GetByNameCaseInsensitive(key.Name); ok { 204 return fmt.Errorf("a foreign key with the name `%s` already exists", key.Name) 205 } 206 if len(key.TableColumns) != len(key.ReferencedTableColumns) { 207 return fmt.Errorf("foreign keys must have the same number of columns declared and referenced") 208 } 209 210 fkc.foreignKeys[key.HashOf().String()] = key 211 } 212 return nil 213 } 214 215 // AllKeys returns a slice, sorted by name ascending, containing all of the foreign keys in this collection. 216 func (fkc *ForeignKeyCollection) AllKeys() []ForeignKey { 217 fks := make([]ForeignKey, len(fkc.foreignKeys)) 218 i := 0 219 for _, fk := range fkc.foreignKeys { 220 fks[i] = fk 221 i++ 222 } 223 sort.Slice(fks, func(i, j int) bool { 224 return fks[i].Name < fks[j].Name 225 }) 226 return fks 227 } 228 229 // Contains returns whether the given foreign key name already exists for this collection. 230 func (fkc *ForeignKeyCollection) Contains(foreignKeyName string) bool { 231 _, ok := fkc.GetByNameCaseInsensitive(foreignKeyName) 232 return ok 233 } 234 235 // Count returns the number of indexes in this collection. 236 func (fkc *ForeignKeyCollection) Count() int { 237 return len(fkc.foreignKeys) 238 } 239 240 // GetByNameCaseInsensitive returns a ForeignKey with a matching case-insensitive name, and whether a match exists. 241 func (fkc *ForeignKeyCollection) GetByNameCaseInsensitive(foreignKeyName string) (ForeignKey, bool) { 242 if foreignKeyName == "" { 243 return ForeignKey{}, false 244 } 245 for _, fk := range fkc.foreignKeys { 246 if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) { 247 return fk, true 248 } 249 } 250 return ForeignKey{}, false 251 } 252 253 // GetByTags gets the Foreign Key defined over the parent and child columns corresponding to tags parameters. 254 func (fkc *ForeignKeyCollection) GetByTags(childTags, parentTags []uint64) (match ForeignKey, ok bool) { 255 _ = fkc.Iter(func(fk ForeignKey) (stop bool, err error) { 256 if len(fk.ReferencedTableColumns) != len(parentTags) { 257 return false, nil 258 } 259 for i, t := range fk.ReferencedTableColumns { 260 if t != parentTags[i] { 261 return false, nil 262 } 263 } 264 265 if len(fk.TableColumns) != len(childTags) { 266 return false, nil 267 } 268 for i, t := range fk.TableColumns { 269 if t != childTags[i] { 270 return false, nil 271 } 272 } 273 match, ok = fk, true 274 return true, nil 275 }) 276 return match, ok 277 } 278 279 func (fkc *ForeignKeyCollection) Iter(cb func(fk ForeignKey) (stop bool, err error)) error { 280 for _, fk := range fkc.foreignKeys { 281 stop, err := cb(fk) 282 if err != nil { 283 return err 284 } 285 if stop { 286 return err 287 } 288 } 289 return nil 290 } 291 292 // KeysForTable returns all foreign keys that reference the given table in some capacity. The returned array 293 // declaredFk contains all foreign keys in which this table declared the foreign key. The array referencedByFk contains 294 // all foreign keys in which this table is the referenced table. If the table contains a self-referential foreign key, 295 // it will be present in both declaresFk and referencedByFk. Each array is sorted by name ascending. 296 func (fkc *ForeignKeyCollection) KeysForTable(tableName string) (declaredFk, referencedByFk []ForeignKey) { 297 lowercaseTblName := strings.ToLower(tableName) 298 for _, foreignKey := range fkc.foreignKeys { 299 if strings.ToLower(foreignKey.TableName) == lowercaseTblName { 300 declaredFk = append(declaredFk, foreignKey) 301 } 302 if strings.ToLower(foreignKey.ReferencedTableName) == lowercaseTblName { 303 referencedByFk = append(referencedByFk, foreignKey) 304 } 305 } 306 sort.Slice(declaredFk, func(i, j int) bool { 307 return declaredFk[i].Name < declaredFk[j].Name 308 }) 309 sort.Slice(referencedByFk, func(i, j int) bool { 310 return referencedByFk[i].Name < referencedByFk[j].Name 311 }) 312 return 313 } 314 315 // Map returns the collection as a Noms Map for persistence. 316 func (fkc *ForeignKeyCollection) Map(ctx context.Context, vrw types.ValueReadWriter) (types.Map, error) { 317 fkMap, err := types.NewMap(ctx, vrw) 318 if err != nil { 319 return types.EmptyMap, err 320 } 321 fkMapEditor := fkMap.Edit() 322 for hashOf, foreignKey := range fkc.foreignKeys { 323 val, err := marshal.Marshal(ctx, vrw, foreignKey) 324 if err != nil { 325 return types.EmptyMap, err 326 } 327 fkMapEditor.Set(types.String(hashOf), val) 328 } 329 return fkMapEditor.Map(ctx) 330 } 331 332 // RemoveKeys removes any Foreign Keys with matching column set from the collection. 333 func (fkc *ForeignKeyCollection) RemoveKeys(fks ...ForeignKey) { 334 drops := set.NewStrSet(nil) 335 for _, outgoing := range fks { 336 for k, existing := range fkc.foreignKeys { 337 if outgoing.EqualDefs(existing) { 338 drops.Add(k) 339 } 340 } 341 } 342 for _, k := range drops.AsSlice() { 343 delete(fkc.foreignKeys, k) 344 } 345 } 346 347 // RemoveKeyByName removes a foreign key from the collection. It does not remove the associated indexes from their 348 // respective tables. 349 func (fkc *ForeignKeyCollection) RemoveKeyByName(foreignKeyName string) error { 350 var key string 351 for k, fk := range fkc.foreignKeys { 352 if strings.ToLower(fk.Name) == strings.ToLower(foreignKeyName) { 353 key = k 354 break 355 } 356 } 357 if key == "" { 358 return fmt.Errorf("`%s` does not exist as a foreign key", foreignKeyName) 359 } 360 delete(fkc.foreignKeys, key) 361 return nil 362 } 363 364 // RemoveTables removes all foreign keys associated with the given tables, if permitted. The operation assumes that ALL 365 // tables to be removed are in a single call, as splitting tables into different calls may result in unintended errors. 366 func (fkc *ForeignKeyCollection) RemoveTables(ctx context.Context, tables ...string) error { 367 outgoing := set.NewStrSet(tables) 368 for _, fk := range fkc.foreignKeys { 369 dropChild := outgoing.Contains(fk.TableName) 370 dropParent := outgoing.Contains(fk.ReferencedTableName) 371 if dropParent && !dropChild { 372 return fmt.Errorf("unable to remove `%s` since it is referenced from table `%s`", fk.ReferencedTableName, fk.TableName) 373 } 374 if dropChild { 375 delete(fkc.foreignKeys, fk.HashOf().String()) 376 } 377 } 378 return nil 379 } 380 381 // RenameTable updates all foreign key entries in the collection with the updated table name. Does not check for name 382 // collisions. 383 func (fkc *ForeignKeyCollection) RenameTable(oldTableName, newTableName string) { 384 updated := make(map[string]ForeignKey, len(fkc.foreignKeys)) 385 for _, fk := range fkc.foreignKeys { 386 if fk.TableName == oldTableName { 387 fk.TableName = newTableName 388 } 389 if fk.ReferencedTableName == oldTableName { 390 fk.ReferencedTableName = newTableName 391 } 392 updated[fk.HashOf().String()] = fk 393 } 394 fkc.foreignKeys = updated 395 } 396 397 // Stage takes the keys to add and remove and updates the current collection. Does not perform any key validation nor 398 // name uniqueness verification, as this is intended for use in commit staging. Adding a foreign key and updating (such 399 // as a table rename) an existing one are functionally the same. 400 func (fkc *ForeignKeyCollection) Stage(ctx context.Context, fksToAdd []ForeignKey, fksToRemove []ForeignKey) { 401 for _, fk := range fksToAdd { 402 fkc.foreignKeys[fk.HashOf().String()] = fk 403 } 404 for _, fk := range fksToRemove { 405 delete(fkc.foreignKeys, fk.HashOf().String()) 406 } 407 } 408 409 // String returns the SQL reference option in uppercase. 410 func (refOp ForeignKeyReferenceOption) String() string { 411 switch refOp { 412 case ForeignKeyReferenceOption_DefaultAction: 413 return "NONE SPECIFIED" 414 case ForeignKeyReferenceOption_Cascade: 415 return "CASCADE" 416 case ForeignKeyReferenceOption_NoAction: 417 return "NO ACTION" 418 case ForeignKeyReferenceOption_Restrict: 419 return "RESTRICT" 420 case ForeignKeyReferenceOption_SetNull: 421 return "SET NULL" 422 default: 423 return "INVALID" 424 } 425 } 426 427 // copy returns an exact copy of the calling collection. As collections are meant to be modified in-place, this ensures 428 // that the original collection is not affected by any operations applied to the copied collection. 429 func (fkc *ForeignKeyCollection) copy() *ForeignKeyCollection { 430 copiedForeignKeys := make(map[string]ForeignKey) 431 for hashOf, key := range fkc.foreignKeys { 432 copiedForeignKeys[hashOf] = key 433 } 434 return &ForeignKeyCollection{copiedForeignKeys} 435 } 436 437 // ValidateData ensures that the foreign key is valid by comparing the index data from the given table 438 // against the index data from the referenced table. 439 func (fk ForeignKey) ValidateData(ctx context.Context, childIdx, parentIdx types.Map, childDef, parentDef schema.Index) error { 440 if fk.ReferencedTableIndex != parentDef.Name() { 441 return fmt.Errorf("cannot validate data as wrong referenced index was given: expected `%s` but received `%s`", 442 fk.ReferencedTableIndex, parentDef.Name()) 443 } 444 445 tagMap := make(map[uint64]uint64, len(fk.TableColumns)) 446 for i, childTag := range fk.TableColumns { 447 tagMap[childTag] = fk.ReferencedTableColumns[i] 448 } 449 450 // FieldMappings ignore columns not in the tagMap 451 fm, err := rowconv.NewFieldMapping(childDef.Schema(), parentDef.Schema(), tagMap) 452 if err != nil { 453 return err 454 } 455 456 vrw := types.NewMemoryValueStore() // We are checking fks rather than persisting any values, so an internal VRW can be used 457 rc, err := rowconv.NewRowConverter(ctx, vrw, fm) 458 if err != nil { 459 return err 460 } 461 462 rdr, err := noms.NewNomsMapReader(ctx, childIdx, childDef.Schema()) 463 if err != nil { 464 return err 465 } 466 467 for { 468 childIdxRow, err := rdr.ReadRow(ctx) 469 if err == io.EOF { 470 break 471 } 472 if err != nil { 473 return err 474 } 475 476 // Check if there are any NULL values, as they should be skipped 477 hasNulls := false 478 _, err = childIdxRow.IterSchema(childDef.Schema(), func(tag uint64, val types.Value) (stop bool, err error) { 479 if types.IsNull(val) { 480 hasNulls = true 481 return true, nil 482 } 483 return false, nil 484 }) 485 if err != nil { 486 return err 487 } 488 if hasNulls { 489 continue 490 } 491 492 parentIdxRow, err := rc.Convert(childIdxRow) 493 if err != nil { 494 return err 495 } 496 if row.IsEmpty(parentIdxRow) { 497 continue 498 } 499 500 partial, err := row.ReduceToIndexPartialKey(parentDef, parentIdxRow) 501 if err != nil { 502 return err 503 } 504 505 indexIter := noms.NewNomsRangeReader(parentDef.Schema(), parentIdx, 506 []*noms.ReadRange{{Start: partial, Inclusive: true, Reverse: false, Check: func(tuple types.Tuple) (bool, error) { 507 return tuple.StartsWith(partial), nil 508 }}}, 509 ) 510 511 switch _, err = indexIter.ReadRow(ctx); err { 512 case nil: 513 continue // parent table contains child key 514 case io.EOF: 515 indexKeyStr, _ := types.EncodedValue(ctx, partial) 516 return fmt.Errorf("foreign key violation on `%s`.`%s`: `%s`", fk.Name, fk.TableName, indexKeyStr) 517 default: 518 return err 519 } 520 } 521 522 return nil 523 }