github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/drop_database.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  
    16  	"github.com/cockroachdb/cockroach/pkg/config"
    17  	"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
    18  	"github.com/cockroachdb/cockroach/pkg/keys"
    19  	"github.com/cockroachdb/cockroach/pkg/kv"
    20  	"github.com/cockroachdb/cockroach/pkg/security"
    21  	"github.com/cockroachdb/cockroach/pkg/server/telemetry"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    28  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    29  	"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
    30  	"github.com/cockroachdb/cockroach/pkg/util/log"
    31  	"github.com/cockroachdb/errors"
    32  )
    33  
    34  type dropDatabaseNode struct {
    35  	n               *tree.DropDatabase
    36  	dbDesc          *sqlbase.DatabaseDescriptor
    37  	td              []toDelete
    38  	schemasToDelete []string
    39  }
    40  
    41  // DropDatabase drops a database.
    42  // Privileges: DROP on database and DROP on all tables in the database.
    43  //   Notes: postgres allows only the database owner to DROP a database.
    44  //          mysql requires the DROP privileges on the database.
    45  // TODO(XisiHuang): our DROP DATABASE is like the postgres DROP SCHEMA
    46  // (cockroach database == postgres schema). the postgres default of not
    47  // dropping the schema if there are dependent objects is more sensible
    48  // (see the RESTRICT and CASCADE options).
    49  func (p *planner) DropDatabase(ctx context.Context, n *tree.DropDatabase) (planNode, error) {
    50  	if n.Name == "" {
    51  		return nil, errEmptyDatabaseName
    52  	}
    53  
    54  	if string(n.Name) == p.SessionData().Database && p.SessionData().SafeUpdates {
    55  		return nil, pgerror.DangerousStatementf("DROP DATABASE on current database")
    56  	}
    57  
    58  	// Check that the database exists.
    59  	dbDesc, err := p.ResolveUncachedDatabaseByName(ctx, string(n.Name), !n.IfExists)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if dbDesc == nil {
    64  		// IfExists was specified and database was not found.
    65  		return newZeroNode(nil /* columns */), nil
    66  	}
    67  
    68  	if err := p.CheckPrivilege(ctx, dbDesc, privilege.DROP); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	schemas, err := p.Tables().GetSchemasForDatabase(ctx, p.txn, dbDesc.ID)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	var tbNames TableNames
    78  	schemasToDelete := make([]string, 0, len(schemas))
    79  	for _, schema := range schemas {
    80  		schemasToDelete = append(schemasToDelete, schema)
    81  		toAppend, err := resolver.GetObjectNames(
    82  			ctx, p.txn, p, p.ExecCfg().Codec, dbDesc, schema, true, /*explicitPrefix*/
    83  		)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		tbNames = append(tbNames, toAppend...)
    88  	}
    89  
    90  	if len(tbNames) > 0 {
    91  		switch n.DropBehavior {
    92  		case tree.DropRestrict:
    93  			return nil, pgerror.Newf(pgcode.DependentObjectsStillExist,
    94  				"database %q is not empty and RESTRICT was specified",
    95  				tree.ErrNameStringP(&dbDesc.Name))
    96  		case tree.DropDefault:
    97  			// The default is CASCADE, however be cautious if CASCADE was
    98  			// not specified explicitly.
    99  			if p.SessionData().SafeUpdates {
   100  				return nil, pgerror.DangerousStatementf(
   101  					"DROP DATABASE on non-empty database without explicit CASCADE")
   102  			}
   103  		}
   104  	}
   105  
   106  	td := make([]toDelete, 0, len(tbNames))
   107  	for i, tbName := range tbNames {
   108  		found, desc, err := p.LookupObject(
   109  			ctx,
   110  			tree.ObjectLookupFlags{
   111  				CommonLookupFlags: tree.CommonLookupFlags{Required: true},
   112  				RequireMutable:    true,
   113  				IncludeOffline:    true,
   114  			},
   115  			tbName.Catalog(),
   116  			tbName.Schema(),
   117  			tbName.Table(),
   118  		)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		if !found {
   123  			continue
   124  		}
   125  		tbDesc, ok := desc.(*sqlbase.MutableTableDescriptor)
   126  		if !ok {
   127  			return nil, errors.AssertionFailedf(
   128  				"descriptor for %q is not MutableTableDescriptor",
   129  				tbName.String(),
   130  			)
   131  		}
   132  		if tbDesc.State == sqlbase.TableDescriptor_OFFLINE {
   133  			return nil, pgerror.Newf(pgcode.ObjectNotInPrerequisiteState,
   134  				"cannot drop a database with OFFLINE tables, ensure %s is"+
   135  					" dropped or made public before dropping database %s",
   136  				tbName.String(), tree.AsString((*tree.Name)(&dbDesc.Name)))
   137  		}
   138  		if err := p.prepareDropWithTableDesc(ctx, tbDesc); err != nil {
   139  			return nil, err
   140  		}
   141  		// Recursively check permissions on all dependent views, since some may
   142  		// be in different databases.
   143  		for _, ref := range tbDesc.DependedOnBy {
   144  			if err := p.canRemoveDependentView(ctx, tbDesc, ref, tree.DropCascade); err != nil {
   145  				return nil, err
   146  			}
   147  		}
   148  		td = append(td, toDelete{&tbNames[i], tbDesc})
   149  	}
   150  
   151  	td, err = p.filterCascadedTables(ctx, td)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return &dropDatabaseNode{n: n, dbDesc: dbDesc, td: td, schemasToDelete: schemasToDelete}, nil
   157  }
   158  
   159  func (n *dropDatabaseNode) startExec(params runParams) error {
   160  	telemetry.Inc(sqltelemetry.SchemaChangeDropCounter("database"))
   161  
   162  	ctx := params.ctx
   163  	p := params.p
   164  	tbNameStrings := make([]string, 0, len(n.td))
   165  	droppedTableDetails := make([]jobspb.DroppedTableDetails, 0, len(n.td))
   166  	tableDescs := make([]*sqlbase.MutableTableDescriptor, 0, len(n.td))
   167  
   168  	for _, toDel := range n.td {
   169  		droppedTableDetails = append(droppedTableDetails, jobspb.DroppedTableDetails{
   170  			Name: toDel.tn.FQString(),
   171  			ID:   toDel.desc.ID,
   172  		})
   173  		tableDescs = append(tableDescs, toDel.desc)
   174  	}
   175  	if err := p.createDropDatabaseJob(
   176  		ctx, n.dbDesc.ID, droppedTableDetails, tree.AsStringWithFQNames(n.n, params.Ann()),
   177  	); err != nil {
   178  		return err
   179  	}
   180  
   181  	// When views, sequences, and tables are dropped, don't queue a separate job
   182  	// for each of them, since the single DROP DATABASE job will cover them all.
   183  	for _, toDel := range n.td {
   184  		desc := toDel.desc
   185  		var cascadedObjects []string
   186  		var err error
   187  		if desc.IsView() {
   188  			// TODO(knz): dependent dropped views should be qualified here.
   189  			cascadedObjects, err = p.dropViewImpl(ctx, desc, false /* queueJob */, "", tree.DropCascade)
   190  		} else if desc.IsSequence() {
   191  			err = p.dropSequenceImpl(ctx, desc, false /* queueJob */, "", tree.DropCascade)
   192  		} else {
   193  			// TODO(knz): dependent dropped table names should be qualified here.
   194  			cascadedObjects, err = p.dropTableImpl(ctx, desc, false /* queueJob */, "")
   195  		}
   196  		if err != nil {
   197  			return err
   198  		}
   199  		tbNameStrings = append(tbNameStrings, cascadedObjects...)
   200  		tbNameStrings = append(tbNameStrings, toDel.tn.FQString())
   201  	}
   202  
   203  	descKey := sqlbase.MakeDescMetadataKey(p.ExecCfg().Codec, n.dbDesc.ID)
   204  
   205  	b := &kv.Batch{}
   206  	if p.ExtendedEvalContext().Tracing.KVTracingEnabled() {
   207  		log.VEventf(ctx, 2, "Del %s", descKey)
   208  	}
   209  	b.Del(descKey)
   210  
   211  	for _, schemaToDelete := range n.schemasToDelete {
   212  		if err := sqlbase.RemoveSchemaNamespaceEntry(
   213  			ctx,
   214  			p.txn,
   215  			p.ExecCfg().Codec,
   216  			n.dbDesc.ID,
   217  			schemaToDelete,
   218  		); err != nil {
   219  			return err
   220  		}
   221  	}
   222  
   223  	err := sqlbase.RemoveDatabaseNamespaceEntry(
   224  		ctx, p.txn, p.ExecCfg().Codec, n.dbDesc.Name, p.ExtendedEvalContext().Tracing.KVTracingEnabled(),
   225  	)
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	// No job was created because no tables were dropped, so zone config can be
   231  	// immediately removed.
   232  	if len(tableDescs) == 0 {
   233  		zoneKeyPrefix := config.MakeZoneKeyPrefix(uint32(n.dbDesc.ID))
   234  		if p.ExtendedEvalContext().Tracing.KVTracingEnabled() {
   235  			log.VEventf(ctx, 2, "DelRange %s", zoneKeyPrefix)
   236  		}
   237  		// Delete the zone config entry for this database.
   238  		b.DelRange(zoneKeyPrefix, zoneKeyPrefix.PrefixEnd(), false /* returnKeys */)
   239  	}
   240  
   241  	p.Tables().AddUncommittedDatabase(n.dbDesc.Name, n.dbDesc.ID, descs.DBDropped)
   242  
   243  	if err := p.txn.Run(ctx, b); err != nil {
   244  		return err
   245  	}
   246  
   247  	if err := p.removeDbComment(ctx, n.dbDesc.ID); err != nil {
   248  		return err
   249  	}
   250  
   251  	// Log Drop Database event. This is an auditable log event and is recorded
   252  	// in the same transaction as the table descriptor update.
   253  	return MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord(
   254  		ctx,
   255  		p.txn,
   256  		EventLogDropDatabase,
   257  		int32(n.dbDesc.ID),
   258  		int32(params.extendedEvalCtx.NodeID.SQLInstanceID()),
   259  		struct {
   260  			DatabaseName         string
   261  			Statement            string
   262  			User                 string
   263  			DroppedSchemaObjects []string
   264  		}{n.n.Name.String(), n.n.String(), p.SessionData().User, tbNameStrings},
   265  	)
   266  }
   267  
   268  func (*dropDatabaseNode) Next(runParams) (bool, error) { return false, nil }
   269  func (*dropDatabaseNode) Close(context.Context)        {}
   270  func (*dropDatabaseNode) Values() tree.Datums          { return tree.Datums{} }
   271  
   272  // filterCascadedTables takes a list of table descriptors and removes any
   273  // descriptors from the list that are dependent on other descriptors in the
   274  // list (e.g. if view v1 depends on table t1, then v1 will be filtered from
   275  // the list).
   276  func (p *planner) filterCascadedTables(ctx context.Context, tables []toDelete) ([]toDelete, error) {
   277  	// Accumulate the set of all tables/views that will be deleted by cascade
   278  	// behavior so that we can filter them out of the list.
   279  	cascadedTables := make(map[sqlbase.ID]bool)
   280  	for _, toDel := range tables {
   281  		desc := toDel.desc
   282  		if err := p.accumulateDependentTables(ctx, cascadedTables, desc); err != nil {
   283  			return nil, err
   284  		}
   285  	}
   286  	filteredTableList := make([]toDelete, 0, len(tables))
   287  	for _, toDel := range tables {
   288  		if !cascadedTables[toDel.desc.ID] {
   289  			filteredTableList = append(filteredTableList, toDel)
   290  		}
   291  	}
   292  	return filteredTableList, nil
   293  }
   294  
   295  func (p *planner) accumulateDependentTables(
   296  	ctx context.Context, dependentTables map[sqlbase.ID]bool, desc *sqlbase.MutableTableDescriptor,
   297  ) error {
   298  	for _, ref := range desc.DependedOnBy {
   299  		dependentTables[ref.ID] = true
   300  		dependentDesc, err := p.Tables().GetMutableTableVersionByID(ctx, ref.ID, p.txn)
   301  		if err != nil {
   302  			return err
   303  		}
   304  		if err := p.accumulateDependentTables(ctx, dependentTables, dependentDesc); err != nil {
   305  			return err
   306  		}
   307  	}
   308  	return nil
   309  }
   310  
   311  func (p *planner) removeDbComment(ctx context.Context, dbID sqlbase.ID) error {
   312  	_, err := p.ExtendedEvalContext().ExecCfg.InternalExecutor.ExecEx(
   313  		ctx,
   314  		"delete-db-comment",
   315  		p.txn,
   316  		sqlbase.InternalExecutorSessionDataOverride{User: security.RootUser},
   317  		"DELETE FROM system.comments WHERE type=$1 AND object_id=$2 AND sub_id=0",
   318  		keys.DatabaseCommentType,
   319  		dbID)
   320  
   321  	return err
   322  }