github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/alter_foreign_key.go (about) 1 // Copyright 2020-2021 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 "sort" 20 "strings" 21 22 "github.com/dolthub/vitess/go/sqltypes" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 "github.com/dolthub/go-mysql-server/sql/types" 26 ) 27 28 type CreateForeignKey struct { 29 // In the cases where we have multiple ALTER statements, we need to resolve the table at execution time rather than 30 // during analysis. Otherwise, you could add a column in the preceding alter and we may have analyzed to a table 31 // that did not yet have that column. 32 DbProvider sql.DatabaseProvider 33 FkDef *sql.ForeignKeyConstraint 34 } 35 36 var _ sql.Node = (*CreateForeignKey)(nil) 37 var _ sql.MultiDatabaser = (*CreateForeignKey)(nil) 38 var _ sql.Databaseable = (*CreateForeignKey)(nil) 39 var _ sql.CollationCoercible = (*CreateForeignKey)(nil) 40 41 func NewAlterAddForeignKey(fkDef *sql.ForeignKeyConstraint) *CreateForeignKey { 42 return &CreateForeignKey{ 43 DbProvider: nil, 44 FkDef: fkDef, 45 } 46 } 47 48 func (p *CreateForeignKey) Database() string { 49 return p.FkDef.Database 50 } 51 52 // Resolved implements the interface sql.Node. 53 func (p *CreateForeignKey) Resolved() bool { 54 return p.DbProvider != nil 55 } 56 57 func (p *CreateForeignKey) IsReadOnly() bool { 58 return false 59 } 60 61 // Children implements the interface sql.Node. 62 func (p *CreateForeignKey) Children() []sql.Node { 63 return nil 64 } 65 66 // WithChildren implements the interface sql.Node. 67 func (p *CreateForeignKey) WithChildren(children ...sql.Node) (sql.Node, error) { 68 return NillaryWithChildren(p, children...) 69 } 70 71 // CheckPrivileges implements the interface sql.Node. 72 func (p *CreateForeignKey) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { 73 subject := sql.PrivilegeCheckSubject{ 74 Database: p.FkDef.ParentDatabase, 75 Table: p.FkDef.ParentTable, 76 } 77 78 return opChecker.UserHasPrivileges(ctx, 79 sql.NewPrivilegedOperation(subject, sql.PrivilegeType_References)) 80 } 81 82 // CollationCoercibility implements the interface sql.CollationCoercible. 83 func (*CreateForeignKey) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 84 return sql.Collation_binary, 7 85 } 86 87 // Schema implements the interface sql.Node. 88 func (p *CreateForeignKey) Schema() sql.Schema { 89 return types.OkResultSchema 90 } 91 92 // DatabaseProvider implements the interface sql.MultiDatabaser. 93 func (p *CreateForeignKey) DatabaseProvider() sql.DatabaseProvider { 94 return p.DbProvider 95 } 96 97 // WithDatabaseProvider implements the interface sql.MultiDatabaser. 98 func (p *CreateForeignKey) WithDatabaseProvider(provider sql.DatabaseProvider) (sql.Node, error) { 99 np := *p 100 np.DbProvider = provider 101 return &np, nil 102 } 103 104 // String implements the interface sql.Node. 105 func (p *CreateForeignKey) String() string { 106 pr := sql.NewTreePrinter() 107 _ = pr.WriteNode("AddForeignKey(%s)", p.FkDef.Name) 108 _ = pr.WriteChildren( 109 fmt.Sprintf("Table(%s.%s)", p.FkDef.Database, p.FkDef.Table), 110 fmt.Sprintf("Columns(%s)", strings.Join(p.FkDef.Columns, ", ")), 111 fmt.Sprintf("ParentTable(%s.%s)", p.FkDef.ParentDatabase, p.FkDef.ParentTable), 112 fmt.Sprintf("ParentColumns(%s)", strings.Join(p.FkDef.ParentColumns, ", ")), 113 fmt.Sprintf("OnUpdate(%s)", p.FkDef.OnUpdate), 114 fmt.Sprintf("OnDelete(%s)", p.FkDef.OnDelete)) 115 return pr.String() 116 } 117 118 // ResolveForeignKey verifies the foreign key definition and resolves the foreign key, creating indexes and validating 119 // data as necessary. 120 // fkChecks - whether to check the foreign key against the data in the table 121 // checkRows - whether to check the existing rows in the table against the foreign key 122 func ResolveForeignKey(ctx *sql.Context, tbl sql.ForeignKeyTable, refTbl sql.ForeignKeyTable, fkDef sql.ForeignKeyConstraint, shouldAdd, fkChecks, checkRows bool) error { 123 if t, ok := tbl.(sql.TemporaryTable); ok && t.IsTemporary() { 124 return sql.ErrTemporaryTablesForeignKeySupport.New() 125 } 126 if fkDef.IsResolved { 127 return fmt.Errorf("cannot resolve foreign key `%s` as it has already been resolved", fkDef.Name) 128 } 129 if len(fkDef.Columns) == 0 { 130 return sql.ErrForeignKeyMissingColumns.New() 131 } 132 if len(fkDef.Columns) != len(fkDef.ParentColumns) { 133 return sql.ErrForeignKeyColumnCountMismatch.New() 134 } 135 136 // Make sure that all columns are valid, in the table, and there are no duplicates 137 cols := make(map[string]*sql.Column) 138 seenCols := make(map[string]struct{}) 139 for _, col := range tbl.Schema() { 140 lowerColName := strings.ToLower(col.Name) 141 cols[lowerColName] = col 142 } 143 for i, fkCol := range fkDef.Columns { 144 lowerFkCol := strings.ToLower(fkCol) 145 col, ok := cols[lowerFkCol] 146 if !ok { 147 return sql.ErrTableColumnNotFound.New(tbl.Name(), fkCol) 148 } 149 _, ok = seenCols[lowerFkCol] 150 if ok { 151 return sql.ErrAddForeignKeyDuplicateColumn.New(fkCol) 152 } 153 // Non-nullable columns may not have SET NULL as a reference option 154 if !col.Nullable && (fkDef.OnUpdate == sql.ForeignKeyReferentialAction_SetNull || fkDef.OnDelete == sql.ForeignKeyReferentialAction_SetNull) { 155 return sql.ErrForeignKeySetNullNonNullable.New(col.Name) 156 } 157 seenCols[lowerFkCol] = struct{}{} 158 fkDef.Columns[i] = col.Name 159 } 160 161 // Do the same for the referenced columns 162 if fkChecks { 163 parentCols := make(map[string]*sql.Column) 164 seenCols = make(map[string]struct{}) 165 for _, col := range refTbl.Schema() { 166 lowerColName := strings.ToLower(col.Name) 167 parentCols[lowerColName] = col 168 } 169 for i, fkParentCol := range fkDef.ParentColumns { 170 lowerFkParentCol := strings.ToLower(fkParentCol) 171 parentCol, ok := parentCols[lowerFkParentCol] 172 if !ok { 173 return sql.ErrTableColumnNotFound.New(fkDef.ParentTable, fkParentCol) 174 } 175 _, ok = seenCols[lowerFkParentCol] 176 if ok { 177 return sql.ErrAddForeignKeyDuplicateColumn.New(fkParentCol) 178 } 179 seenCols[lowerFkParentCol] = struct{}{} 180 fkDef.ParentColumns[i] = parentCol.Name 181 } 182 183 // Check that the types align and are valid 184 for i := range fkDef.Columns { 185 col := cols[strings.ToLower(fkDef.Columns[i])] 186 parentCol := parentCols[strings.ToLower(fkDef.ParentColumns[i])] 187 if !foreignKeyComparableTypes(ctx, col.Type, parentCol.Type) { 188 return sql.ErrForeignKeyColumnTypeMismatch.New(fkDef.Columns[i], fkDef.ParentColumns[i]) 189 } 190 sqlParserType := col.Type.Type() 191 if sqlParserType == sqltypes.Text || sqlParserType == sqltypes.Blob { 192 return sql.ErrForeignKeyTextBlob.New() 193 } 194 } 195 196 // Ensure that a suitable index exists on the referenced table, and check the declaring table for a suitable index. 197 refTblIndex, ok, err := FindFKIndexWithPrefix(ctx, refTbl, fkDef.ParentColumns, true) 198 if err != nil { 199 return err 200 } 201 if !ok { 202 return sql.ErrForeignKeyMissingReferenceIndex.New(fkDef.Name, fkDef.ParentTable) 203 } 204 205 indexPositions, appendTypes, err := FindForeignKeyColMapping(ctx, fkDef.Name, tbl, fkDef.Columns, fkDef.ParentColumns, refTblIndex) 206 if err != nil { 207 return err 208 } 209 var selfCols map[string]int 210 if fkDef.IsSelfReferential() { 211 selfCols = make(map[string]int) 212 for i, col := range tbl.Schema() { 213 selfCols[strings.ToLower(col.Name)] = i 214 } 215 } 216 reference := &ForeignKeyReferenceHandler{ 217 ForeignKey: fkDef, 218 SelfCols: selfCols, 219 RowMapper: ForeignKeyRowMapper{ 220 Index: refTblIndex, 221 Updater: refTbl.GetForeignKeyEditor(ctx), 222 SourceSch: tbl.Schema(), 223 IndexPositions: indexPositions, 224 AppendTypes: appendTypes, 225 }, 226 } 227 228 if checkRows { 229 if err := reference.CheckTable(ctx, tbl); err != nil { 230 return err 231 } 232 } 233 } 234 235 // Check if the current foreign key name has already been used. Rather than checking the table first (which is the 236 // highest cost part of creating a foreign key), we'll check the name if it needs to be checked. If the foreign key 237 // was previously added, we don't need to check the name. 238 if shouldAdd { 239 existingFks, err := tbl.GetDeclaredForeignKeys(ctx) 240 if err != nil { 241 return err 242 } 243 fkLowerName := strings.ToLower(fkDef.Name) 244 for _, existingFk := range existingFks { 245 if fkLowerName == strings.ToLower(existingFk.Name) { 246 return sql.ErrForeignKeyDuplicateName.New(fkDef.Name) 247 } 248 } 249 } 250 251 _, ok, err := FindFKIndexWithPrefix(ctx, tbl, fkDef.Columns, false) 252 if err != nil { 253 return err 254 } 255 if !ok { 256 indexColumns := make([]sql.IndexColumn, len(fkDef.Columns)) 257 for i, col := range fkDef.Columns { 258 indexColumns[i] = sql.IndexColumn{ 259 Name: col, 260 Length: 0, 261 } 262 } 263 indexMap := make(map[string]struct{}) 264 indexes, err := tbl.GetIndexes(ctx) 265 if err != nil { 266 return err 267 } 268 for _, index := range indexes { 269 indexMap[strings.ToLower(index.ID())] = struct{}{} 270 } 271 indexName := strings.Join(fkDef.Columns, "") 272 if _, ok = indexMap[strings.ToLower(indexName)]; ok { 273 for i := 0; true; i++ { 274 newIndexName := fmt.Sprintf("%s_%d", indexName, i) 275 if _, ok = indexMap[strings.ToLower(newIndexName)]; !ok { 276 indexName = newIndexName 277 break 278 } 279 } 280 } 281 err = tbl.CreateIndexForForeignKey(ctx, sql.IndexDef{ 282 Name: indexName, 283 Columns: indexColumns, 284 Constraint: sql.IndexConstraint_None, 285 Storage: sql.IndexUsing_Default, 286 }) 287 if err != nil { 288 return err 289 } 290 } 291 292 if shouldAdd { 293 fkDef.IsResolved = fkChecks 294 return tbl.AddForeignKey(ctx, fkDef) 295 } else { 296 fkDef.IsResolved = fkChecks 297 return tbl.UpdateForeignKey(ctx, fkDef.Name, fkDef) 298 } 299 } 300 301 type DropForeignKey struct { 302 // In the cases where we have multiple ALTER statements, we need to resolve the table at execution time rather than 303 // during analysis. Otherwise, you could add a foreign key in the preceding alter and we may have analyzed to a 304 // table that did not yet have that foreign key. 305 DbProvider sql.DatabaseProvider 306 database string 307 Table string 308 Name string 309 } 310 311 var _ sql.Node = (*DropForeignKey)(nil) 312 var _ sql.MultiDatabaser = (*DropForeignKey)(nil) 313 var _ sql.Databaseable = (*DropForeignKey)(nil) 314 var _ sql.CollationCoercible = (*DropForeignKey)(nil) 315 316 func NewAlterDropForeignKey(db, table, name string) *DropForeignKey { 317 return &DropForeignKey{ 318 DbProvider: nil, 319 database: db, 320 Table: table, 321 Name: name, 322 } 323 } 324 325 func (p *DropForeignKey) Database() string { 326 return p.database 327 } 328 329 // WithChildren implements the interface sql.Node. 330 func (p *DropForeignKey) WithChildren(children ...sql.Node) (sql.Node, error) { 331 return NillaryWithChildren(p, children...) 332 } 333 334 // CheckPrivileges implements the interface sql.Node. 335 func (p *DropForeignKey) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { 336 subject := sql.PrivilegeCheckSubject{ 337 Database: p.database, 338 Table: p.Table, 339 } 340 return opChecker.UserHasPrivileges(ctx, 341 sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Alter)) 342 } 343 344 // CollationCoercibility implements the interface sql.CollationCoercible. 345 func (*DropForeignKey) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 346 return sql.Collation_binary, 7 347 } 348 349 // Schema implements the interface sql.Node. 350 func (p *DropForeignKey) Schema() sql.Schema { 351 return types.OkResultSchema 352 } 353 354 // DatabaseProvider implements the interface sql.MultiDatabaser. 355 func (p *DropForeignKey) DatabaseProvider() sql.DatabaseProvider { 356 return p.DbProvider 357 } 358 359 // WithDatabaseProvider implements the interface sql.MultiDatabaser. 360 func (p *DropForeignKey) WithDatabaseProvider(provider sql.DatabaseProvider) (sql.Node, error) { 361 np := *p 362 np.DbProvider = provider 363 return &np, nil 364 } 365 366 // Resolved implements the interface sql.Node. 367 func (p *DropForeignKey) Resolved() bool { 368 return p.DbProvider != nil 369 } 370 371 func (p *DropForeignKey) IsReadOnly() bool { 372 return false 373 } 374 375 // Children implements the interface sql.Node. 376 func (p *DropForeignKey) Children() []sql.Node { 377 return nil 378 } 379 380 // String implements the interface sql.Node. 381 func (p *DropForeignKey) String() string { 382 pr := sql.NewTreePrinter() 383 _ = pr.WriteNode("DropForeignKey(%s)", p.Name) 384 _ = pr.WriteChildren(fmt.Sprintf("Table(%s.%s)", p.Database(), p.Table)) 385 return pr.String() 386 } 387 388 // FindForeignKeyColMapping returns the mapping from a given row to its equivalent index position, based on the matching 389 // foreign key columns. This also verifies that the column types match, as it is a prerequisite for mapping. For foreign 390 // keys that do not match the full index, also returns the types to append during the key mapping, as all index columns 391 // must have a column expression. All strings are case-insensitive. 392 func FindForeignKeyColMapping( 393 ctx *sql.Context, 394 fkName string, 395 localTbl sql.ForeignKeyTable, 396 localFKCols []string, 397 destFKCols []string, 398 index sql.Index, 399 ) ([]int, []sql.Type, error) { 400 localFKCols = lowercaseSlice(localFKCols) 401 destFKCols = lowercaseSlice(destFKCols) 402 destTblName := strings.ToLower(index.Table()) 403 404 localSchTypeMap := make(map[string]sql.Type) 405 localSchPositionMap := make(map[string]int) 406 for i, col := range localTbl.Schema() { 407 colName := strings.ToLower(col.Name) 408 localSchTypeMap[colName] = col.Type 409 localSchPositionMap[colName] = i 410 } 411 var appendTypes []sql.Type 412 indexTypeMap := make(map[string]sql.Type) 413 indexColMap := make(map[string]int) 414 var columnExpressionTypes []sql.ColumnExpressionType 415 if extendedIndex, ok := index.(sql.ExtendedIndex); ok { 416 columnExpressionTypes = extendedIndex.ExtendedColumnExpressionTypes() 417 } else { 418 columnExpressionTypes = index.ColumnExpressionTypes() 419 } 420 for i, indexCol := range columnExpressionTypes { 421 indexColName := strings.ToLower(indexCol.Expression) 422 indexTypeMap[indexColName] = indexCol.Type 423 indexColMap[indexColName] = i 424 if i >= len(destFKCols) { 425 appendTypes = append(appendTypes, indexCol.Type) 426 } 427 } 428 indexPositions := make([]int, len(destFKCols)) 429 430 for fkIdx, colName := range localFKCols { 431 localRowPos, ok := localSchPositionMap[colName] 432 if !ok { 433 // Will happen if a column is renamed that is referenced by a foreign key 434 //TODO: enforce that renaming a column referenced by a foreign key updates that foreign key 435 return nil, nil, fmt.Errorf("column `%s` in foreign key `%s` cannot be found", 436 colName, fkName) 437 } 438 expectedType := localSchTypeMap[colName] 439 destFkCol := destTblName + "." + destFKCols[fkIdx] 440 indexPos, ok := indexColMap[destFkCol] 441 if !ok { 442 // Same as above, renaming a referenced column would cause this error 443 return nil, nil, fmt.Errorf("index column `%s` in foreign key `%s` cannot be found", 444 destFKCols[fkIdx], fkName) 445 } 446 if !foreignKeyComparableTypes(ctx, indexTypeMap[destFkCol], expectedType) { 447 return nil, nil, sql.ErrForeignKeyColumnTypeMismatch.New(colName, destFkCol) 448 } 449 indexPositions[indexPos] = localRowPos 450 } 451 return indexPositions, appendTypes, nil 452 } 453 454 // FindFKIndexWithPrefix returns an index that has the given columns as a prefix, with the index intended for use with 455 // foreign keys. The returned index is deterministic and follows the given rules, from the highest priority in descending order: 456 // 457 // 1. Columns exactly match the index 458 // 2. Columns match as much of the index prefix as possible 459 // 3. Unique index before non-unique 460 // 4. Largest index by column count 461 // 5. Index ID in ascending order 462 // 463 // The prefix columns may be in any order, and the returned index will contain all of the prefix columns within its 464 // prefix. For example, the slices [col1, col2] and [col2, col1] will match the same index, as their ordering does not 465 // matter. The index [col1, col2, col3] would match, but the index [col1, col3] would not match as it is missing "col2". 466 // Prefix columns are case-insensitive. 467 // 468 // If `useExtendedIndexes` is true, then this will include any implicit primary keys that were not explicitly defined on 469 // the index. Some operations only consider explicitly indexed columns, while others also consider any implicit primary 470 // keys as well, therefore this is a boolean to control the desired behavior. 471 func FindFKIndexWithPrefix(ctx *sql.Context, tbl sql.IndexAddressableTable, prefixCols []string, useExtendedIndexes bool, ignoredIndexes ...string) (sql.Index, bool, error) { 472 type idxWithLen struct { 473 sql.Index 474 colLen int 475 } 476 477 ignoredIndexesMap := make(map[string]struct{}) 478 for _, ignoredIndex := range ignoredIndexes { 479 ignoredIndexesMap[strings.ToLower(ignoredIndex)] = struct{}{} 480 } 481 482 indexes, err := tbl.GetIndexes(ctx) 483 if err != nil { 484 return nil, false, err 485 } 486 // Ignore indexes with prefix lengths; they are unsupported in MySQL 487 // https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html#:~:text=Index%20prefixes%20on%20foreign%20key%20columns%20are%20not%20supported. 488 // Ignore spatial indexes; MySQL will not pick them as the underlying secondary index for foreign keys 489 for _, idx := range indexes { 490 if len(idx.PrefixLengths()) > 0 || idx.IsSpatial() || idx.IsFullText() { 491 ignoredIndexesMap[strings.ToLower(idx.ID())] = struct{}{} 492 } 493 } 494 tblName := strings.ToLower(tbl.Name()) 495 exprCols := make([]string, len(prefixCols)) 496 for i, prefixCol := range prefixCols { 497 exprCols[i] = tblName + "." + strings.ToLower(prefixCol) 498 } 499 colLen := len(exprCols) 500 var indexesWithLen []idxWithLen 501 for _, idx := range indexes { 502 if _, ok := ignoredIndexesMap[strings.ToLower(idx.ID())]; ok { 503 continue 504 } 505 var indexExprs []string 506 if extendedIdx, ok := idx.(sql.ExtendedIndex); ok && useExtendedIndexes { 507 indexExprs = lowercaseSlice(extendedIdx.ExtendedExpressions()) 508 } else { 509 indexExprs = lowercaseSlice(idx.Expressions()) 510 } 511 if ok := exprsAreIndexPrefix(exprCols, indexExprs); ok { 512 indexesWithLen = append(indexesWithLen, idxWithLen{idx, len(indexExprs)}) 513 } 514 } 515 if len(indexesWithLen) == 0 { 516 return nil, false, nil 517 } 518 519 sort.Slice(indexesWithLen, func(i, j int) bool { 520 idxI := indexesWithLen[i] 521 idxJ := indexesWithLen[j] 522 if idxI.colLen == colLen && idxJ.colLen != colLen { 523 return true 524 } else if idxI.colLen != colLen && idxJ.colLen == colLen { 525 return false 526 } else if idxI.Index.IsUnique() != idxJ.Index.IsUnique() { 527 return idxI.Index.IsUnique() 528 } else if idxI.colLen != idxJ.colLen { 529 return idxI.colLen > idxJ.colLen 530 } else { 531 return idxI.Index.ID() < idxJ.Index.ID() 532 } 533 }) 534 sortedIndexes := make([]sql.Index, len(indexesWithLen)) 535 for i := 0; i < len(sortedIndexes); i++ { 536 sortedIndexes[i] = indexesWithLen[i].Index 537 } 538 return sortedIndexes[0], true, nil 539 } 540 541 // foreignKeyComparableTypes returns whether the two given types are able to be used as parent/child columns in a 542 // foreign key. 543 func foreignKeyComparableTypes(ctx *sql.Context, type1 sql.Type, type2 sql.Type) bool { 544 if !type1.Equals(type2) { 545 // There seems to be a special case where CHAR/VARCHAR/BINARY/VARBINARY can have unequal lengths. 546 // Have not tested every type nor combination, but this seems specific to those 4 types. 547 if type1.Type() == type2.Type() { 548 switch type1.Type() { 549 case sqltypes.Char, sqltypes.VarChar, sqltypes.Binary, sqltypes.VarBinary: 550 type1String := type1.(sql.StringType) 551 type2String := type2.(sql.StringType) 552 if type1String.Collation().CharacterSet() != type2String.Collation().CharacterSet() { 553 return false 554 } 555 default: 556 return false 557 } 558 } else { 559 return false 560 } 561 } 562 return true 563 } 564 565 // exprsAreIndexPrefix returns whether the given expressions are a prefix of the given index expressions 566 func exprsAreIndexPrefix(exprs, indexExprs []string) bool { 567 if len(exprs) > len(indexExprs) { 568 return false 569 } 570 571 for i := 0; i < len(exprs); i++ { 572 if exprs[i] != indexExprs[i] { 573 return false 574 } 575 } 576 577 return true 578 } 579 580 func lowercaseSlice(strs []string) []string { 581 newStrs := make([]string, len(strs)) 582 for i, str := range strs { 583 newStrs[i] = strings.ToLower(str) 584 } 585 return newStrs 586 }