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  }