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) {}