github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/truncate.go (about)

     1  // Copyright 2015 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  
    16  	"github.com/cockroachdb/cockroach/pkg/config"
    17  	"github.com/cockroachdb/cockroach/pkg/keys"
    18  	"github.com/cockroachdb/cockroach/pkg/kv"
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/security"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/catalogkv"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/row"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    27  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    28  	"github.com/cockroachdb/cockroach/pkg/util/log"
    29  	"github.com/cockroachdb/errors"
    30  )
    31  
    32  // TableTruncateChunkSize is the maximum number of keys deleted per chunk
    33  // during a table truncation.
    34  const TableTruncateChunkSize = indexTruncateChunkSize
    35  
    36  type truncateNode struct {
    37  	n *tree.Truncate
    38  }
    39  
    40  // Truncate deletes all rows from a table.
    41  // Privileges: DROP on table.
    42  //   Notes: postgres requires TRUNCATE.
    43  //          mysql requires DROP (for mysql >= 5.1.16, DELETE before that).
    44  func (p *planner) Truncate(ctx context.Context, n *tree.Truncate) (planNode, error) {
    45  	return &truncateNode{n: n}, nil
    46  }
    47  
    48  func (t *truncateNode) startExec(params runParams) error {
    49  	p := params.p
    50  	n := t.n
    51  	ctx := params.ctx
    52  
    53  	// Since truncation may cascade to a given table any number of times, start by
    54  	// building the unique set (ID->name) of tables to truncate.
    55  	toTruncate := make(map[sqlbase.ID]string, len(n.Tables))
    56  	// toTraverse is the list of tables whose references need to be traversed
    57  	// while constructing the list of tables that should be truncated.
    58  	toTraverse := make([]sqlbase.MutableTableDescriptor, 0, len(n.Tables))
    59  
    60  	for i := range n.Tables {
    61  		tn := &n.Tables[i]
    62  		tableDesc, err := p.ResolveMutableTableDescriptor(
    63  			ctx, tn, true /*required*/, resolver.ResolveRequireTableDesc)
    64  		if err != nil {
    65  			return err
    66  		}
    67  
    68  		if err := p.CheckPrivilege(ctx, tableDesc, privilege.DROP); err != nil {
    69  			return err
    70  		}
    71  
    72  		toTruncate[tableDesc.ID] = tn.FQString()
    73  		toTraverse = append(toTraverse, *tableDesc)
    74  	}
    75  
    76  	// Check that any referencing tables are contained in the set, or, if CASCADE
    77  	// requested, add them all to the set.
    78  	for len(toTraverse) > 0 {
    79  		// Pick last element.
    80  		idx := len(toTraverse) - 1
    81  		tableDesc := toTraverse[idx]
    82  		toTraverse = toTraverse[:idx]
    83  
    84  		maybeEnqueue := func(tableID sqlbase.ID, msg string) error {
    85  			// Check if we're already truncating the referencing table.
    86  			if _, ok := toTruncate[tableID]; ok {
    87  				return nil
    88  			}
    89  			other, err := p.Tables().GetMutableTableVersionByID(ctx, tableID, p.txn)
    90  			if err != nil {
    91  				return err
    92  			}
    93  
    94  			if n.DropBehavior != tree.DropCascade {
    95  				return errors.Errorf("%q is %s table %q", tableDesc.Name, msg, other.Name)
    96  			}
    97  			if err := p.CheckPrivilege(ctx, other, privilege.DROP); err != nil {
    98  				return err
    99  			}
   100  			otherName, err := p.getQualifiedTableName(ctx, other.TableDesc())
   101  			if err != nil {
   102  				return err
   103  			}
   104  			toTruncate[other.ID] = otherName
   105  			toTraverse = append(toTraverse, *other)
   106  			return nil
   107  		}
   108  
   109  		for i := range tableDesc.InboundFKs {
   110  			fk := &tableDesc.InboundFKs[i]
   111  			if err := maybeEnqueue(fk.OriginTableID, "referenced by foreign key from"); err != nil {
   112  				return err
   113  			}
   114  		}
   115  		for _, idx := range tableDesc.AllNonDropIndexes() {
   116  			for _, ref := range idx.InterleavedBy {
   117  				if err := maybeEnqueue(ref.Table, "interleaved by"); err != nil {
   118  					return err
   119  				}
   120  			}
   121  		}
   122  	}
   123  
   124  	// Mark this query as non-cancellable if autocommitting.
   125  	if err := p.cancelChecker.Check(); err != nil {
   126  		return err
   127  	}
   128  
   129  	traceKV := p.extendedEvalCtx.Tracing.KVTracingEnabled()
   130  	for id, name := range toTruncate {
   131  		if err := p.truncateTable(ctx, id, tree.AsStringWithFQNames(t.n, params.Ann()), traceKV); err != nil {
   132  			return err
   133  		}
   134  
   135  		// Log a Truncate Table event for this table.
   136  		if err := MakeEventLogger(p.extendedEvalCtx.ExecCfg).InsertEventRecord(
   137  			ctx,
   138  			p.txn,
   139  			EventLogTruncateTable,
   140  			int32(id),
   141  			int32(p.extendedEvalCtx.NodeID.SQLInstanceID()),
   142  			struct {
   143  				TableName string
   144  				Statement string
   145  				User      string
   146  			}{name, n.String(), p.SessionData().User},
   147  		); err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func (t *truncateNode) Next(runParams) (bool, error) { return false, nil }
   156  func (t *truncateNode) Values() tree.Datums          { return tree.Datums{} }
   157  func (t *truncateNode) Close(context.Context)        {}
   158  
   159  // truncateTable truncates the data of a table in a single transaction. It
   160  // drops the table and recreates it with a new ID. The dropped table is
   161  // GC-ed later through an asynchronous schema change.
   162  func (p *planner) truncateTable(
   163  	ctx context.Context, id sqlbase.ID, jobDesc string, traceKV bool,
   164  ) error {
   165  	// Read the table descriptor because it might have changed
   166  	// while another table in the truncation list was truncated.
   167  	tableDesc, err := p.Tables().GetMutableTableVersionByID(ctx, id, p.txn)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	// tableDesc.DropJobID = dropJobID
   172  	newTableDesc := sqlbase.NewMutableCreatedTableDescriptor(tableDesc.TableDescriptor)
   173  	newTableDesc.ReplacementOf = sqlbase.TableDescriptor_Replacement{
   174  		ID: id,
   175  		// NB: Time is just used for debugging purposes. See the comment on the
   176  		// field for more details.
   177  		Time: p.txn.ReadTimestamp(),
   178  	}
   179  	newTableDesc.SetID(0)
   180  	newTableDesc.Version = 1
   181  
   182  	// Remove old name -> id map.
   183  	// This is a violation of consistency because once the TRUNCATE commits
   184  	// some nodes in the cluster can have cached the old name to id map
   185  	// for the table and applying operations using the old table id.
   186  	// This violation is needed because it is not uncommon for TRUNCATE
   187  	// to be used along with other CRUD commands that follow it in the
   188  	// same transaction. Commands that follow the TRUNCATE in the same
   189  	// transaction will use the correct descriptor (through uncommittedTables)
   190  	// See the comment about problem 3 related to draining names in
   191  	// structured.proto
   192  	//
   193  	// TODO(vivek): Fix properly along with #12123.
   194  	zoneKey := config.MakeZoneKey(uint32(tableDesc.ID))
   195  	key := sqlbase.MakeObjectNameKey(
   196  		ctx, p.ExecCfg().Settings,
   197  		newTableDesc.ParentID,
   198  		newTableDesc.GetParentSchemaID(),
   199  		newTableDesc.Name,
   200  	).Key(p.ExecCfg().Codec)
   201  
   202  	// Remove the old namespace entry.
   203  	if err := sqlbase.RemoveObjectNamespaceEntry(
   204  		ctx, p.txn, p.execCfg.Codec,
   205  		tableDesc.ParentID, tableDesc.GetParentSchemaID(), tableDesc.GetName(),
   206  		traceKV); err != nil {
   207  		return err
   208  	}
   209  
   210  	// Drop table.
   211  	if err := p.initiateDropTable(ctx, tableDesc, true /* queueJob */, jobDesc, false /* drainName */); err != nil {
   212  		return err
   213  	}
   214  
   215  	newID, err := catalogkv.GenerateUniqueDescID(ctx, p.ExecCfg().DB, p.ExecCfg().Codec)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	// update all the references to this table.
   221  	tables, err := p.findAllReferences(ctx, *tableDesc)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	if changed, err := reassignReferencedTables(tables, tableDesc.ID, newID); err != nil {
   226  		return err
   227  	} else if changed {
   228  		newTableDesc.State = sqlbase.TableDescriptor_ADD
   229  	}
   230  
   231  	for _, table := range tables {
   232  		// TODO (lucy): Have more consistent/informative names for dependent jobs.
   233  		if err := p.writeSchemaChange(
   234  			ctx, table, sqlbase.InvalidMutationID, "updating reference for truncated table",
   235  		); err != nil {
   236  			return err
   237  		}
   238  	}
   239  
   240  	// Reassign all self references.
   241  	if changed, err := reassignReferencedTables(
   242  		[]*sqlbase.MutableTableDescriptor{newTableDesc}, tableDesc.ID, newID,
   243  	); err != nil {
   244  		return err
   245  	} else if changed {
   246  		newTableDesc.State = sqlbase.TableDescriptor_ADD
   247  	}
   248  
   249  	// Resolve all outstanding mutations. Make all new schema elements
   250  	// public because the table is empty and doesn't need to be backfilled.
   251  	for _, m := range newTableDesc.Mutations {
   252  		if err := newTableDesc.MakeMutationComplete(m); err != nil {
   253  			return err
   254  		}
   255  	}
   256  	newTableDesc.Mutations = nil
   257  	newTableDesc.GCMutations = nil
   258  	// NB: Set the modification time to a zero value so that it is interpreted
   259  	// as the commit timestamp for the new descriptor. See the comment on
   260  	// sqlbase.Descriptor.Table().
   261  	newTableDesc.ModificationTime = hlc.Timestamp{}
   262  	// TODO (lucy): Have more consistent/informative names for dependent jobs.
   263  	if err := p.createDescriptorWithID(
   264  		ctx, key, newID, newTableDesc, p.ExtendedEvalContext().Settings,
   265  		"creating new descriptor for truncated table",
   266  	); err != nil {
   267  		return err
   268  	}
   269  
   270  	// Reassign comments on the table, columns and indexes.
   271  	if err := reassignComments(ctx, p, tableDesc, newTableDesc); err != nil {
   272  		return err
   273  	}
   274  
   275  	// Copy the zone config.
   276  	b := &kv.Batch{}
   277  	b.Get(zoneKey)
   278  	if err := p.txn.Run(ctx, b); err != nil {
   279  		return err
   280  	}
   281  	val := b.Results[0].Rows[0].Value
   282  	if val == nil {
   283  		return nil
   284  	}
   285  	zoneCfg, err := val.GetBytes()
   286  	if err != nil {
   287  		return err
   288  	}
   289  	const insertZoneCfg = `INSERT INTO system.zones (id, config) VALUES ($1, $2)`
   290  	_, err = p.ExtendedEvalContext().ExecCfg.InternalExecutor.Exec(
   291  		ctx, "insert-zone", p.txn, insertZoneCfg, newID, zoneCfg)
   292  	return err
   293  }
   294  
   295  // For all the references from a table
   296  func (p *planner) findAllReferences(
   297  	ctx context.Context, table sqlbase.MutableTableDescriptor,
   298  ) ([]*sqlbase.MutableTableDescriptor, error) {
   299  	refs, err := table.FindAllReferences()
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	tables := make([]*sqlbase.MutableTableDescriptor, 0, len(refs))
   304  	for id := range refs {
   305  		if id == table.ID {
   306  			continue
   307  		}
   308  		t, err := p.Tables().GetMutableTableVersionByID(ctx, id, p.txn)
   309  		if err != nil {
   310  			return nil, err
   311  		}
   312  		tables = append(tables, t)
   313  	}
   314  
   315  	return tables, nil
   316  }
   317  
   318  // reassign all the references from oldID to newID.
   319  func reassignReferencedTables(
   320  	tables []*sqlbase.MutableTableDescriptor, oldID, newID sqlbase.ID,
   321  ) (bool, error) {
   322  	changed := false
   323  	for _, table := range tables {
   324  		if err := table.ForeachNonDropIndex(func(index *sqlbase.IndexDescriptor) error {
   325  			for j, a := range index.Interleave.Ancestors {
   326  				if a.TableID == oldID {
   327  					index.Interleave.Ancestors[j].TableID = newID
   328  					changed = true
   329  				}
   330  			}
   331  			for j, c := range index.InterleavedBy {
   332  				if c.Table == oldID {
   333  					index.InterleavedBy[j].Table = newID
   334  					changed = true
   335  				}
   336  			}
   337  			return nil
   338  		}); err != nil {
   339  			return false, err
   340  		}
   341  		for i := range table.OutboundFKs {
   342  			fk := &table.OutboundFKs[i]
   343  			if fk.ReferencedTableID == oldID {
   344  				fk.ReferencedTableID = newID
   345  				changed = true
   346  			}
   347  		}
   348  		for i := range table.InboundFKs {
   349  			fk := &table.InboundFKs[i]
   350  			if fk.OriginTableID == oldID {
   351  				fk.OriginTableID = newID
   352  				changed = true
   353  			}
   354  		}
   355  
   356  		for i, dest := range table.DependsOn {
   357  			if dest == oldID {
   358  				table.DependsOn[i] = newID
   359  				changed = true
   360  			}
   361  		}
   362  		origRefs := table.DependedOnBy
   363  		table.DependedOnBy = nil
   364  		for _, ref := range origRefs {
   365  			if ref.ID == oldID {
   366  				ref.ID = newID
   367  				changed = true
   368  			}
   369  			table.DependedOnBy = append(table.DependedOnBy, ref)
   370  		}
   371  	}
   372  	return changed, nil
   373  }
   374  
   375  // reassignComments reassign all comments on the table, indexes and columns.
   376  func reassignComments(
   377  	ctx context.Context, p *planner, oldTableDesc, newTableDesc *sqlbase.MutableTableDescriptor,
   378  ) error {
   379  	_, err := p.ExtendedEvalContext().ExecCfg.InternalExecutor.ExecEx(
   380  		ctx,
   381  		"update-table-comments",
   382  		p.txn,
   383  		sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   384  		`UPDATE system.comments SET object_id=$1 WHERE object_id=$2`,
   385  		newTableDesc.ID,
   386  		oldTableDesc.ID,
   387  	)
   388  	return err
   389  }
   390  
   391  // ClearTableDataInChunks truncates the data of a table in chunks. It deletes a
   392  // range of data for the table, which includes the PK and all indexes.
   393  // The table has already been marked for deletion and has been purged from the
   394  // descriptor cache on all nodes.
   395  //
   396  // TODO(vivek): No node is reading/writing data on the table at this stage,
   397  // therefore the entire table can be deleted with no concern for conflicts (we
   398  // can even eliminate the need to use a transaction for each chunk at a later
   399  // stage if it proves inefficient).
   400  func ClearTableDataInChunks(
   401  	ctx context.Context,
   402  	db *kv.DB,
   403  	codec keys.SQLCodec,
   404  	tableDesc *sqlbase.TableDescriptor,
   405  	traceKV bool,
   406  ) error {
   407  	const chunkSize = TableTruncateChunkSize
   408  	var resume roachpb.Span
   409  	alloc := &sqlbase.DatumAlloc{}
   410  	for rowIdx, done := 0, false; !done; rowIdx += chunkSize {
   411  		resumeAt := resume
   412  		if traceKV {
   413  			log.VEventf(ctx, 2, "table %s truncate at row: %d, span: %s", tableDesc.Name, rowIdx, resume)
   414  		}
   415  		if err := db.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
   416  			rd, err := row.MakeDeleter(
   417  				ctx,
   418  				txn,
   419  				codec,
   420  				sqlbase.NewImmutableTableDescriptor(*tableDesc),
   421  				nil,
   422  				nil,
   423  				row.SkipFKs,
   424  				nil, /* *tree.EvalContext */
   425  				alloc,
   426  			)
   427  			if err != nil {
   428  				return err
   429  			}
   430  			td := tableDeleter{rd: rd, alloc: alloc}
   431  			if err := td.init(ctx, txn, nil /* *tree.EvalContext */); err != nil {
   432  				return err
   433  			}
   434  			resume, err = td.deleteAllRows(ctx, resumeAt, chunkSize, traceKV)
   435  			return err
   436  		}); err != nil {
   437  			return err
   438  		}
   439  		done = resume.Key == nil
   440  	}
   441  	return nil
   442  }
   443  
   444  // canClearRangeForDrop returns if an index can be deleted by deleting every
   445  // key from a single span.
   446  // This determines whether an index is dropped during a schema change, or if
   447  // it is only deleted upon GC.
   448  func canClearRangeForDrop(index *sqlbase.IndexDescriptor) bool {
   449  	return !index.IsInterleaved()
   450  }