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 }