github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/drop_index.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 "fmt" 16 "strings" 17 18 "github.com/cockroachdb/cockroach/pkg/clusterversion" 19 "github.com/cockroachdb/cockroach/pkg/roachpb" 20 "github.com/cockroachdb/cockroach/pkg/server/telemetry" 21 "github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver" 22 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 23 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgnotice" 25 "github.com/cockroachdb/cockroach/pkg/sql/privilege" 26 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 27 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 28 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 29 "github.com/cockroachdb/cockroach/pkg/util/hlc" 30 "github.com/cockroachdb/errors" 31 ) 32 33 type dropIndexNode struct { 34 n *tree.DropIndex 35 idxNames []fullIndexName 36 } 37 38 // DropIndex drops an index. 39 // Privileges: CREATE on table. 40 // Notes: postgres allows only the index owner to DROP an index. 41 // mysql requires the INDEX privilege on the table. 42 func (p *planner) DropIndex(ctx context.Context, n *tree.DropIndex) (planNode, error) { 43 // Keep a track of the indexes that exist to check. When the IF EXISTS 44 // options are provided, we will simply not include any indexes that 45 // don't exist and continue execution. 46 idxNames := make([]fullIndexName, 0, len(n.IndexList)) 47 for _, index := range n.IndexList { 48 tn, tableDesc, err := expandMutableIndexName(ctx, p, index, !n.IfExists /* requireTable */) 49 if err != nil { 50 // Error or table did not exist. 51 return nil, err 52 } 53 if tableDesc == nil { 54 // IfExists specified and table did not exist. 55 continue 56 } 57 58 if err := p.CheckPrivilege(ctx, tableDesc, privilege.CREATE); err != nil { 59 return nil, err 60 } 61 62 idxNames = append(idxNames, fullIndexName{tn: tn, idxName: index.Index}) 63 } 64 return &dropIndexNode{n: n, idxNames: idxNames}, nil 65 } 66 67 // ReadingOwnWrites implements the planNodeReadingOwnWrites interface. 68 // This is because DROP INDEX performs multiple KV operations on descriptors 69 // and expects to see its own writes. 70 func (n *dropIndexNode) ReadingOwnWrites() {} 71 72 func (n *dropIndexNode) startExec(params runParams) error { 73 telemetry.Inc(sqltelemetry.SchemaChangeDropCounter("index")) 74 75 if n.n.Concurrently { 76 params.p.SendClientNotice( 77 params.ctx, 78 pgnotice.Newf("CONCURRENTLY is not required as all indexes are dropped concurrently"), 79 ) 80 } 81 82 ctx := params.ctx 83 for _, index := range n.idxNames { 84 // Need to retrieve the descriptor again for each index name in 85 // the list: when two or more index names refer to the same table, 86 // the mutation list and new version number created by the first 87 // drop need to be visible to the second drop. 88 tableDesc, err := params.p.ResolveMutableTableDescriptor( 89 ctx, index.tn, true /*required*/, resolver.ResolveRequireTableDesc) 90 if sqlbase.IsUndefinedRelationError(err) { 91 // Somehow the descriptor we had during planning is not there 92 // any more. 93 return errors.NewAssertionErrorWithWrappedErrf(err, 94 "table descriptor for %q became unavailable within same txn", 95 tree.ErrString(index.tn)) 96 } 97 if err != nil { 98 return err 99 } 100 101 // If we couldn't find the index by name, this is either a legitimate error or 102 // this statement contains an 'IF EXISTS' qualifier. Both of these cases are 103 // handled by `dropIndexByName()` below so we just ignore the error here. 104 idxDesc, dropped, _ := tableDesc.FindIndexByName(string(index.idxName)) 105 var shardColName string 106 // If we're dropping a sharded index, record the name of its shard column to 107 // potentially drop it if no other index refers to it. 108 if idxDesc != nil && idxDesc.IsSharded() && !dropped { 109 shardColName = idxDesc.Sharded.Name 110 } 111 112 if err := params.p.dropIndexByName( 113 ctx, index.tn, index.idxName, tableDesc, n.n.IfExists, n.n.DropBehavior, checkIdxConstraint, 114 tree.AsStringWithFQNames(n.n, params.Ann()), 115 ); err != nil { 116 return err 117 } 118 119 if shardColName != "" { 120 if err := n.maybeDropShardColumn(params, tableDesc, shardColName); err != nil { 121 return err 122 } 123 } 124 } 125 return nil 126 } 127 128 // dropShardColumnAndConstraint drops the given shard column and its associated check 129 // constraint. 130 func (n *dropIndexNode) dropShardColumnAndConstraint( 131 params runParams, 132 tableDesc *sqlbase.MutableTableDescriptor, 133 shardColDesc *sqlbase.ColumnDescriptor, 134 ) error { 135 validChecks := tableDesc.Checks[:0] 136 for _, check := range tableDesc.AllActiveAndInactiveChecks() { 137 if used, err := check.UsesColumn(tableDesc.TableDesc(), shardColDesc.ID); err != nil { 138 return err 139 } else if used { 140 if check.Validity == sqlbase.ConstraintValidity_Validating { 141 return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, 142 "referencing constraint %q in the middle of being added, try again later", check.Name) 143 } 144 } else { 145 validChecks = append(validChecks, check) 146 } 147 } 148 149 if len(validChecks) != len(tableDesc.Checks) { 150 tableDesc.Checks = validChecks 151 } 152 153 tableDesc.AddColumnMutation(shardColDesc, sqlbase.DescriptorMutation_DROP) 154 for i := range tableDesc.Columns { 155 if tableDesc.Columns[i].ID == shardColDesc.ID { 156 tmp := tableDesc.Columns[:0] 157 for j, col := range tableDesc.Columns { 158 if i == j { 159 continue 160 } 161 tmp = append(tmp, col) 162 } 163 tableDesc.Columns = tmp 164 break 165 } 166 } 167 168 if err := tableDesc.AllocateIDs(); err != nil { 169 return err 170 } 171 mutationID := tableDesc.ClusterVersion.NextMutationID 172 if err := params.p.writeSchemaChange( 173 params.ctx, tableDesc, mutationID, tree.AsStringWithFQNames(n.n, params.Ann()), 174 ); err != nil { 175 return err 176 } 177 return nil 178 } 179 180 // maybeDropShardColumn drops the given shard column, if there aren't any other indexes 181 // referring to it. 182 // 183 // Assumes that the given index is sharded. 184 func (n *dropIndexNode) maybeDropShardColumn( 185 params runParams, tableDesc *sqlbase.MutableTableDescriptor, shardColName string, 186 ) error { 187 shardColDesc, dropped, err := tableDesc.FindColumnByName(tree.Name(shardColName)) 188 if err != nil { 189 return err 190 } 191 if dropped { 192 return nil 193 } 194 shouldDropShardColumn := true 195 for _, otherIdx := range tableDesc.AllNonDropIndexes() { 196 if otherIdx.ContainsColumnID(shardColDesc.ID) { 197 shouldDropShardColumn = false 198 break 199 } 200 } 201 if !shouldDropShardColumn { 202 return nil 203 } 204 return n.dropShardColumnAndConstraint(params, tableDesc, shardColDesc) 205 } 206 207 func (*dropIndexNode) Next(runParams) (bool, error) { return false, nil } 208 func (*dropIndexNode) Values() tree.Datums { return tree.Datums{} } 209 func (*dropIndexNode) Close(context.Context) {} 210 211 type fullIndexName struct { 212 tn *tree.TableName 213 idxName tree.UnrestrictedName 214 } 215 216 // dropIndexConstraintBehavior is used when dropping an index to signal whether 217 // it is okay to do so even if it is in use as a constraint (outbound FK or 218 // unique). This is a subset of what is implied by DropBehavior CASCADE, which 219 // implies dropping *all* dependencies. This is used e.g. when the element 220 // constrained is being dropped anyway. 221 type dropIndexConstraintBehavior bool 222 223 const ( 224 checkIdxConstraint dropIndexConstraintBehavior = true 225 ignoreIdxConstraint dropIndexConstraintBehavior = false 226 ) 227 228 func (p *planner) dropIndexByName( 229 ctx context.Context, 230 tn *tree.TableName, 231 idxName tree.UnrestrictedName, 232 tableDesc *sqlbase.MutableTableDescriptor, 233 ifExists bool, 234 behavior tree.DropBehavior, 235 constraintBehavior dropIndexConstraintBehavior, 236 jobDesc string, 237 ) error { 238 idx, dropped, err := tableDesc.FindIndexByName(string(idxName)) 239 if err != nil { 240 // Only index names of the form "table@idx" throw an error here if they 241 // don't exist. 242 if ifExists { 243 // Noop. 244 return nil 245 } 246 // Index does not exist, but we want it to: error out. 247 return err 248 } 249 if dropped { 250 return nil 251 } 252 253 if idx.Unique && behavior != tree.DropCascade && constraintBehavior != ignoreIdxConstraint && !idx.CreatedExplicitly { 254 return errors.WithHint( 255 pgerror.Newf(pgcode.DependentObjectsStillExist, 256 "index %q is in use as unique constraint", idx.Name), 257 "use CASCADE if you really want to drop it.", 258 ) 259 } 260 261 // Check if requires CCL binary for eventual zone config removal. 262 _, zone, _, err := GetZoneConfigInTxn(ctx, p.txn, uint32(tableDesc.ID), nil, "", false) 263 if err != nil { 264 return err 265 } 266 267 for _, s := range zone.Subzones { 268 if s.IndexID != uint32(idx.ID) { 269 _, err = GenerateSubzoneSpans( 270 p.ExecCfg().Settings, 271 p.ExecCfg().ClusterID(), 272 p.ExecCfg().Codec, 273 tableDesc.TableDesc(), 274 zone.Subzones, 275 false, /* newSubzones */ 276 ) 277 if sqlbase.IsCCLRequiredError(err) { 278 return sqlbase.NewCCLRequiredError(fmt.Errorf("schema change requires a CCL binary "+ 279 "because table %q has at least one remaining index or partition with a zone config", 280 tableDesc.Name)) 281 } 282 break 283 } 284 } 285 286 // Remove all foreign key references and backreferences from the index. 287 // TODO (lucy): This is incorrect for two reasons: The first is that FKs won't 288 // be restored if the DROP INDEX is rolled back, and the second is that 289 // validated constraints should be dropped in the schema changer in multiple 290 // steps to avoid inconsistencies. We should be queuing a mutation to drop the 291 // FK instead. The reason why the FK is removed here is to keep the index 292 // state consistent with the removal of the reference on the other table 293 // involved in the FK, in case of rollbacks (#38733). 294 295 // TODO (rohany): switching all the checks from checking the legacy ID's to 296 // checking if the index has a prefix of the columns needed for the foreign 297 // key might result in some false positives for this index while it is in 298 // a mixed version cluster, but we have to remove all reads of the legacy 299 // explicit index fields. 300 301 // Construct a list of all the remaining indexes, so that we can see if there 302 // is another index that could replace the one we are deleting for a given 303 // foreign key constraint. 304 remainingIndexes := make([]*sqlbase.IndexDescriptor, 0, len(tableDesc.Indexes)+1) 305 remainingIndexes = append(remainingIndexes, &tableDesc.PrimaryIndex) 306 for i := range tableDesc.Indexes { 307 index := &tableDesc.Indexes[i] 308 if index.ID != idx.ID { 309 remainingIndexes = append(remainingIndexes, index) 310 } 311 } 312 313 // indexHasReplacementCandidate runs isValidIndex on each index in remainingIndexes and returns 314 // true if at least one index satisfies isValidIndex. 315 indexHasReplacementCandidate := func(isValidIndex func(*sqlbase.IndexDescriptor) bool) bool { 316 foundReplacement := false 317 for _, index := range remainingIndexes { 318 if isValidIndex(index) { 319 foundReplacement = true 320 break 321 } 322 } 323 return foundReplacement 324 } 325 // If we aren't at the cluster version where we have removed explicit foreign key IDs 326 // from the foreign key descriptors, fall back to the existing drop index logic. 327 // That means we pretend that we can never find replacements for any indexes. 328 if !p.ExecCfg().Settings.Version.IsActive(ctx, clusterversion.VersionNoExplicitForeignKeyIndexIDs) { 329 indexHasReplacementCandidate = func(func(*sqlbase.IndexDescriptor) bool) bool { 330 return false 331 } 332 } 333 334 // Check for foreign key mutations referencing this index. 335 for _, m := range tableDesc.Mutations { 336 if c := m.GetConstraint(); c != nil && 337 c.ConstraintType == sqlbase.ConstraintToUpdate_FOREIGN_KEY && 338 // If the index being deleted could be used as a index for this outbound 339 // foreign key mutation, then make sure that we have another index that 340 // could be used for this mutation. 341 idx.IsValidOriginIndex(c.ForeignKey.OriginColumnIDs) && 342 !indexHasReplacementCandidate(func(idx *sqlbase.IndexDescriptor) bool { 343 return idx.IsValidOriginIndex(c.ForeignKey.OriginColumnIDs) 344 }) { 345 return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, 346 "referencing constraint %q in the middle of being added, try again later", c.ForeignKey.Name) 347 } 348 } 349 350 if err := p.MaybeUpgradeDependentOldForeignKeyVersionTables(ctx, tableDesc); err != nil { 351 return err 352 } 353 354 // Index for updating the FK slices in place when removing FKs. 355 sliceIdx := 0 356 for i := range tableDesc.OutboundFKs { 357 tableDesc.OutboundFKs[sliceIdx] = tableDesc.OutboundFKs[i] 358 sliceIdx++ 359 fk := &tableDesc.OutboundFKs[i] 360 canReplace := func(idx *sqlbase.IndexDescriptor) bool { 361 return idx.IsValidOriginIndex(fk.OriginColumnIDs) 362 } 363 // The index being deleted could be used as the origin index for this foreign key. 364 if idx.IsValidOriginIndex(fk.OriginColumnIDs) && !indexHasReplacementCandidate(canReplace) { 365 if behavior != tree.DropCascade && constraintBehavior != ignoreIdxConstraint { 366 return errors.Errorf("index %q is in use as a foreign key constraint", idx.Name) 367 } 368 sliceIdx-- 369 if err := p.removeFKBackReference(ctx, tableDesc, fk); err != nil { 370 return err 371 } 372 } 373 } 374 tableDesc.OutboundFKs = tableDesc.OutboundFKs[:sliceIdx] 375 376 sliceIdx = 0 377 for i := range tableDesc.InboundFKs { 378 tableDesc.InboundFKs[sliceIdx] = tableDesc.InboundFKs[i] 379 sliceIdx++ 380 fk := &tableDesc.InboundFKs[i] 381 canReplace := func(idx *sqlbase.IndexDescriptor) bool { 382 return idx.IsValidReferencedIndex(fk.ReferencedColumnIDs) 383 } 384 // The index being deleted could potentially be the referenced index for this fk. 385 if idx.IsValidReferencedIndex(fk.ReferencedColumnIDs) && 386 // If we haven't found a replacement candidate for this foreign key, then 387 // we need a cascade to delete this index. 388 !indexHasReplacementCandidate(canReplace) { 389 // If we found haven't found a replacement, then we check that the drop behavior is cascade. 390 if err := p.canRemoveFKBackreference(ctx, idx.Name, fk, behavior); err != nil { 391 return err 392 } 393 sliceIdx-- 394 if err := p.removeFKForBackReference(ctx, tableDesc, fk); err != nil { 395 return err 396 } 397 } 398 } 399 tableDesc.InboundFKs = tableDesc.InboundFKs[:sliceIdx] 400 401 if len(idx.Interleave.Ancestors) > 0 { 402 if err := p.removeInterleaveBackReference(ctx, tableDesc, idx); err != nil { 403 return err 404 } 405 } 406 for _, ref := range idx.InterleavedBy { 407 if err := p.removeInterleave(ctx, ref); err != nil { 408 return err 409 } 410 } 411 412 var droppedViews []string 413 for _, tableRef := range tableDesc.DependedOnBy { 414 if tableRef.IndexID == idx.ID { 415 // Ensure that we have DROP privilege on all dependent views 416 err := p.canRemoveDependentViewGeneric( 417 ctx, "index", idx.Name, tableDesc.ParentID, tableRef, behavior) 418 if err != nil { 419 return err 420 } 421 viewDesc, err := p.getViewDescForCascade( 422 ctx, "index", idx.Name, tableDesc.ParentID, tableRef.ID, behavior, 423 ) 424 if err != nil { 425 return err 426 } 427 viewJobDesc := fmt.Sprintf("removing view %q dependent on index %q which is being dropped", 428 viewDesc.Name, idx.Name) 429 cascadedViews, err := p.removeDependentView(ctx, tableDesc, viewDesc, viewJobDesc) 430 if err != nil { 431 return err 432 } 433 droppedViews = append(droppedViews, viewDesc.Name) 434 droppedViews = append(droppedViews, cascadedViews...) 435 } 436 } 437 438 // Overwriting tableDesc.Index may mess up with the idx object we collected above. Make a copy. 439 idxCopy := *idx 440 idx = &idxCopy 441 442 found := false 443 for i, idxEntry := range tableDesc.Indexes { 444 if idxEntry.ID == idx.ID { 445 // Unsplit all manually split ranges in the index so they can be 446 // automatically merged by the merge queue. 447 span := tableDesc.IndexSpan(p.ExecCfg().Codec, idxEntry.ID) 448 ranges, err := ScanMetaKVs(ctx, p.txn, span) 449 if err != nil { 450 return err 451 } 452 for _, r := range ranges { 453 var desc roachpb.RangeDescriptor 454 if err := r.ValueProto(&desc); err != nil { 455 return err 456 } 457 // We have to explicitly check that the range descriptor's start key 458 // lies within the span of the index since ScanMetaKVs returns all 459 // intersecting spans. 460 if (desc.GetStickyBit() != hlc.Timestamp{}) && span.Key.Compare(desc.StartKey.AsRawKey()) <= 0 { 461 // Swallow "key is not the start of a range" errors because it would 462 // mean that the sticky bit was removed and merged concurrently. DROP 463 // INDEX should not fail because of this. 464 if err := p.ExecCfg().DB.AdminUnsplit(ctx, desc.StartKey); err != nil && !strings.Contains(err.Error(), "is not the start of a range") { 465 return err 466 } 467 } 468 } 469 470 // the idx we picked up with FindIndexByID at the top may not 471 // contain the same field any more due to other schema changes 472 // intervening since the initial lookup. So we send the recent 473 // copy idxEntry for drop instead. 474 if err := tableDesc.AddIndexMutation(&idxEntry, sqlbase.DescriptorMutation_DROP); err != nil { 475 return err 476 } 477 tableDesc.Indexes = append(tableDesc.Indexes[:i], tableDesc.Indexes[i+1:]...) 478 found = true 479 break 480 } 481 } 482 if !found { 483 return fmt.Errorf("index %q in the middle of being added, try again later", idxName) 484 } 485 486 if err := p.removeIndexComment(ctx, tableDesc.ID, idx.ID); err != nil { 487 return err 488 } 489 490 if err := tableDesc.Validate(ctx, p.txn, p.ExecCfg().Codec); err != nil { 491 return err 492 } 493 mutationID := tableDesc.ClusterVersion.NextMutationID 494 if err := p.writeSchemaChange(ctx, tableDesc, mutationID, jobDesc); err != nil { 495 return err 496 } 497 p.SendClientNotice( 498 ctx, 499 errors.WithHint( 500 pgnotice.Newf("the data for dropped indexes is reclaimed asynchronously"), 501 "The reclamation delay can be customized in the zone configuration for the table.", 502 ), 503 ) 504 // Record index drop in the event log. This is an auditable log event 505 // and is recorded in the same transaction as the table descriptor 506 // update. 507 return MakeEventLogger(p.extendedEvalCtx.ExecCfg).InsertEventRecord( 508 ctx, 509 p.txn, 510 EventLogDropIndex, 511 int32(tableDesc.ID), 512 int32(p.extendedEvalCtx.NodeID.SQLInstanceID()), 513 struct { 514 TableName string 515 IndexName string 516 Statement string 517 User string 518 MutationID uint32 519 CascadeDroppedViews []string 520 }{tn.FQString(), string(idxName), jobDesc, p.SessionData().User, uint32(mutationID), 521 droppedViews}, 522 ) 523 }