github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rename_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  	"fmt"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/util"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  type renameDatabaseNode struct {
    28  	dbDesc  *sqlbase.DatabaseDescriptor
    29  	newName string
    30  }
    31  
    32  // RenameDatabase renames the database.
    33  // Privileges: superuser, DROP on source database.
    34  //   Notes: postgres requires superuser, db owner, or "CREATEDB".
    35  //          mysql >= 5.1.23 does not allow database renames.
    36  func (p *planner) RenameDatabase(ctx context.Context, n *tree.RenameDatabase) (planNode, error) {
    37  	if n.Name == "" || n.NewName == "" {
    38  		return nil, errEmptyDatabaseName
    39  	}
    40  
    41  	if string(n.Name) == p.SessionData().Database && p.SessionData().SafeUpdates {
    42  		return nil, pgerror.DangerousStatementf("RENAME DATABASE on current database")
    43  	}
    44  
    45  	if err := p.RequireAdminRole(ctx, "ALTER DATABASE ... RENAME"); err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	dbDesc, err := p.ResolveUncachedDatabaseByName(ctx, string(n.Name), true /*required*/)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	if err := p.CheckPrivilege(ctx, dbDesc, privilege.DROP); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	if n.Name == n.NewName {
    59  		// Noop.
    60  		return newZeroNode(nil /* columns */), nil
    61  	}
    62  
    63  	return &renameDatabaseNode{
    64  		dbDesc:  dbDesc,
    65  		newName: string(n.NewName),
    66  	}, nil
    67  }
    68  
    69  // ReadingOwnWrites implements the planNodeReadingOwnWrites interface.
    70  // This is because RENAME DATABASE performs multiple KV operations on descriptors
    71  // and expects to see its own writes.
    72  func (n *renameDatabaseNode) ReadingOwnWrites() {}
    73  
    74  func (n *renameDatabaseNode) startExec(params runParams) error {
    75  	p := params.p
    76  	ctx := params.ctx
    77  	dbDesc := n.dbDesc
    78  
    79  	// Check if any other tables depend on tables in the database.
    80  	// Because our views and sequence defaults are currently just stored as
    81  	// strings, they (may) explicitly specify the database name.
    82  	// Rather than trying to rewrite them with the changed DB name, we
    83  	// simply disallow such renames for now.
    84  	// See #34416.
    85  	phyAccessor := p.PhysicalSchemaAccessor()
    86  	lookupFlags := p.CommonLookupFlags(true /*required*/)
    87  	// DDL statements bypass the cache.
    88  	lookupFlags.AvoidCached = true
    89  	schemas, err := p.Tables().GetSchemasForDatabase(ctx, p.txn, dbDesc.ID)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	for _, schema := range schemas {
    94  		tbNames, err := phyAccessor.GetObjectNames(
    95  			ctx,
    96  			p.txn,
    97  			p.ExecCfg().Codec,
    98  			dbDesc,
    99  			schema,
   100  			tree.DatabaseListFlags{
   101  				CommonLookupFlags: lookupFlags,
   102  				ExplicitPrefix:    true,
   103  			},
   104  		)
   105  		if err != nil {
   106  			return err
   107  		}
   108  		lookupFlags.Required = false
   109  		for i := range tbNames {
   110  			objDesc, err := phyAccessor.GetObjectDesc(
   111  				ctx,
   112  				p.txn,
   113  				p.ExecCfg().Settings,
   114  				p.ExecCfg().Codec,
   115  				tbNames[i].Catalog(),
   116  				tbNames[i].Schema(),
   117  				tbNames[i].Table(),
   118  				tree.ObjectLookupFlags{CommonLookupFlags: lookupFlags},
   119  			)
   120  			if err != nil {
   121  				return err
   122  			}
   123  			if objDesc == nil {
   124  				continue
   125  			}
   126  			tbDesc := objDesc.TableDesc()
   127  			for _, dependedOn := range tbDesc.DependedOnBy {
   128  				dependentDesc, err := sqlbase.GetTableDescFromID(ctx, p.txn, p.ExecCfg().Codec, dependedOn.ID)
   129  				if err != nil {
   130  					return err
   131  				}
   132  
   133  				isAllowed, referencedCol, err := isAllowedDependentDescInRenameDatabase(
   134  					ctx,
   135  					dependedOn,
   136  					tbDesc,
   137  					dependentDesc,
   138  					dbDesc.Name,
   139  				)
   140  				if err != nil {
   141  					return err
   142  				}
   143  				if isAllowed {
   144  					continue
   145  				}
   146  
   147  				tbTableName := tree.MakeTableNameWithSchema(
   148  					tree.Name(dbDesc.Name),
   149  					tree.Name(schema),
   150  					tree.Name(tbDesc.Name),
   151  				)
   152  				var dependentDescQualifiedString string
   153  				if dbDesc.ID != dependentDesc.ParentID || tbDesc.GetParentSchemaID() != dependentDesc.GetParentSchemaID() {
   154  					var err error
   155  					dependentDescQualifiedString, err = p.getQualifiedTableName(ctx, dependentDesc)
   156  					if err != nil {
   157  						log.Warningf(
   158  							ctx,
   159  							"unable to retrieve fully-qualified name of %s (id: %d): %v",
   160  							tbTableName.String(),
   161  							dependentDesc.ID,
   162  							err,
   163  						)
   164  						return sqlbase.NewDependentObjectErrorf(
   165  							"cannot rename database because a relation depends on relation %q",
   166  							tbTableName.String())
   167  					}
   168  				} else {
   169  					dependentDescTableName := tree.MakeTableNameWithSchema(
   170  						tree.Name(dbDesc.Name),
   171  						tree.Name(schema),
   172  						tree.Name(dependentDesc.Name),
   173  					)
   174  					dependentDescQualifiedString = dependentDescTableName.String()
   175  				}
   176  				depErr := sqlbase.NewDependentObjectErrorf(
   177  					"cannot rename database because relation %q depends on relation %q",
   178  					dependentDescQualifiedString,
   179  					tbTableName.String(),
   180  				)
   181  
   182  				// We can have a more specific error message for sequences.
   183  				if tbDesc.IsSequence() {
   184  					hint := fmt.Sprintf(
   185  						"you can drop the column default %q of %q referencing %q",
   186  						referencedCol,
   187  						tbTableName.String(),
   188  						dependentDescQualifiedString,
   189  					)
   190  					if dependentDesc.GetParentID() == dbDesc.ID {
   191  						hint += fmt.Sprintf(
   192  							" or modify the default to not reference the database name %q",
   193  							dbDesc.Name,
   194  						)
   195  					}
   196  					return errors.WithHint(depErr, hint)
   197  				}
   198  
   199  				// Otherwise, we default to the view error message.
   200  				return errors.WithHintf(depErr,
   201  					"you can drop %q instead", dependentDescQualifiedString)
   202  			}
   203  		}
   204  	}
   205  
   206  	return p.renameDatabase(ctx, dbDesc, n.newName)
   207  }
   208  
   209  // isAllowedDependentDescInRename determines when rename database is allowed with
   210  // a given {tbDesc, dependentDesc} with the relationship dependedOn on a db named dbName.
   211  // Returns a bool representing whether it's allowed, a string indicating the column name
   212  // found to contain the database (if it exists), and an error if any.
   213  // This is a workaround for #45411 until #34416 is resolved.
   214  func isAllowedDependentDescInRenameDatabase(
   215  	ctx context.Context,
   216  	dependedOn sqlbase.TableDescriptor_Reference,
   217  	tbDesc *sqlbase.TableDescriptor,
   218  	dependentDesc *sqlbase.TableDescriptor,
   219  	dbName string,
   220  ) (bool, string, error) {
   221  	// If it is a sequence, and it does not contain the database name, then we have
   222  	// no reason to block it's deletion.
   223  	if !tbDesc.IsSequence() {
   224  		return false, "", nil
   225  	}
   226  
   227  	colIDs := util.MakeFastIntSet()
   228  	for _, colID := range dependedOn.ColumnIDs {
   229  		colIDs.Add(int(colID))
   230  	}
   231  
   232  	for _, column := range dependentDesc.Columns {
   233  		if !colIDs.Contains(int(column.ID)) {
   234  			continue
   235  		}
   236  		colIDs.Remove(int(column.ID))
   237  
   238  		if column.DefaultExpr == nil {
   239  			return false, "", errors.AssertionFailedf(
   240  				"rename_database: expected column id %d in table id %d to have a default expr",
   241  				dependedOn.ID,
   242  				dependentDesc.ID,
   243  			)
   244  		}
   245  		// Try parse the default expression and find the table name direct reference.
   246  		parsedExpr, err := parser.ParseExpr(*column.DefaultExpr)
   247  		if err != nil {
   248  			return false, "", err
   249  		}
   250  		typedExpr, err := tree.TypeCheck(ctx, parsedExpr, nil, column.Type)
   251  		if err != nil {
   252  			return false, "", err
   253  		}
   254  		seqNames, err := getUsedSequenceNames(typedExpr)
   255  		if err != nil {
   256  			return false, "", err
   257  		}
   258  		for _, seqName := range seqNames {
   259  			parsedSeqName, err := parser.ParseTableName(seqName)
   260  			if err != nil {
   261  				return false, "", err
   262  			}
   263  			// There must be at least two parts for this to work.
   264  			if parsedSeqName.NumParts >= 2 {
   265  				// We only don't allow this if the database name is in there.
   266  				// This is always the last argument.
   267  				if tree.Name(parsedSeqName.Parts[parsedSeqName.NumParts-1]).Normalize() == tree.Name(dbName).Normalize() {
   268  					return false, column.Name, nil
   269  				}
   270  			}
   271  		}
   272  	}
   273  	if colIDs.Len() > 0 {
   274  		return false, "", errors.AssertionFailedf(
   275  			"expected to find column ids %s in table id %d",
   276  			colIDs.String(),
   277  			dependentDesc.ID,
   278  		)
   279  	}
   280  	return true, "", nil
   281  }
   282  
   283  func (n *renameDatabaseNode) Next(runParams) (bool, error) { return false, nil }
   284  func (n *renameDatabaseNode) Values() tree.Datums          { return tree.Datums{} }
   285  func (n *renameDatabaseNode) Close(context.Context)        {}