Skip to content

Commit

Permalink
sql: support ALTER TABLE SET SCHEMA command
Browse files Browse the repository at this point in the history
Release note (sql change): Added support for
ALTER TABLE/SEQUENCE/VIEW SET SCHEMA to set the schema of
the table to the target schema.

One must have DROP privilege on the table and CREATE privilege
on the schema to perform the operation.
  • Loading branch information
RichardJCai committed Aug 3, 2020
1 parent b9bed22 commit 655c4cb
Show file tree
Hide file tree
Showing 15 changed files with 674 additions and 111 deletions.
15 changes: 15 additions & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,7 @@ alter_table_stmt ::=
| alter_scatter_stmt
| alter_zone_table_stmt
| alter_rename_table_stmt
| alter_table_set_schema_stmt

alter_index_stmt ::=
alter_oneindex_stmt
Expand All @@ -1038,10 +1039,12 @@ alter_index_stmt ::=

alter_view_stmt ::=
alter_rename_view_stmt
| alter_view_set_schema_stmt

alter_sequence_stmt ::=
alter_rename_sequence_stmt
| alter_sequence_options_stmt
| alter_sequence_set_schema_stmt

alter_database_stmt ::=
alter_rename_database_stmt
Expand Down Expand Up @@ -1376,6 +1379,10 @@ alter_rename_table_stmt ::=
'ALTER' 'TABLE' relation_expr 'RENAME' 'TO' table_name
| 'ALTER' 'TABLE' 'IF' 'EXISTS' relation_expr 'RENAME' 'TO' table_name

alter_table_set_schema_stmt ::=
'ALTER' 'TABLE' relation_expr 'SET' 'SCHEMA' schema_name
| 'ALTER' 'TABLE' 'IF' 'EXISTS' relation_expr 'SET' 'SCHEMA' schema_name

alter_oneindex_stmt ::=
'ALTER' 'INDEX' table_index_name alter_index_cmds
| 'ALTER' 'INDEX' 'IF' 'EXISTS' table_index_name alter_index_cmds
Expand Down Expand Up @@ -1403,6 +1410,10 @@ alter_rename_view_stmt ::=
'ALTER' 'VIEW' relation_expr 'RENAME' 'TO' view_name
| 'ALTER' 'VIEW' 'IF' 'EXISTS' relation_expr 'RENAME' 'TO' view_name

alter_view_set_schema_stmt ::=
'ALTER' 'VIEW' relation_expr 'SET' 'SCHEMA' schema_name
| 'ALTER' 'VIEW' 'IF' 'EXISTS' relation_expr 'SET' 'SCHEMA' schema_name

alter_rename_sequence_stmt ::=
'ALTER' 'SEQUENCE' relation_expr 'RENAME' 'TO' sequence_name
| 'ALTER' 'SEQUENCE' 'IF' 'EXISTS' relation_expr 'RENAME' 'TO' sequence_name
Expand All @@ -1411,6 +1422,10 @@ alter_sequence_options_stmt ::=
'ALTER' 'SEQUENCE' sequence_name sequence_option_list
| 'ALTER' 'SEQUENCE' 'IF' 'EXISTS' sequence_name sequence_option_list

alter_sequence_set_schema_stmt ::=
'ALTER' 'SEQUENCE' relation_expr 'SET' 'SCHEMA' schema_name
| 'ALTER' 'SEQUENCE' 'IF' 'EXISTS' relation_expr 'SET' 'SCHEMA' schema_name

alter_rename_database_stmt ::=
'ALTER' 'DATABASE' database_name 'RENAME' 'TO' database_name

Expand Down
5 changes: 3 additions & 2 deletions pkg/sql/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,8 +711,9 @@ func (n *alterTableNode) startExec(params runParams) error {
}

depViewRenameError := func(objType string, refTableID sqlbase.ID) error {
return params.p.dependentViewRenameError(params.ctx,
objType, tree.ErrString(&t.NewName), n.tableDesc.ParentID, refTableID)
return params.p.dependentViewError(params.ctx,
objType, tree.ErrString(&t.NewName), n.tableDesc.ParentID, refTableID, "rename",
)
}

if err := n.tableDesc.RenameConstraint(
Expand Down
178 changes: 178 additions & 0 deletions pkg/sql/alter_table_set_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package sql

import (
"context"

"github.com/cockroachdb/cockroach/pkg/kv"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util/log"
)

type alterTableSetSchemaNode struct {
newSchema string
tableDesc *sqlbase.MutableTableDescriptor
n *tree.AlterTableSetSchema
}

// AlterTableSetSchema sets the schema for a table, view or sequence.
// Privileges: DROP on source table/view/sequence, CREATE on destination schema.
func (p *planner) AlterTableSetSchema(
ctx context.Context, n *tree.AlterTableSetSchema,
) (planNode, error) {
tn := n.Name.ToTableName()
requiredTableKind := tree.ResolveAnyTableKind
if n.IsView {
requiredTableKind = tree.ResolveRequireViewDesc
} else if n.IsSequence {
requiredTableKind = tree.ResolveRequireSequenceDesc
}
tableDesc, err := p.ResolveMutableTableDescriptor(
ctx, &tn, !n.IfExists, requiredTableKind)
if err != nil {
return nil, err
}
if tableDesc == nil {
// Noop.
return newZeroNode(nil /* columns */), nil
}

// The user needs DROP privilege on the table to set the schema.
err = p.CheckPrivilege(ctx, tableDesc, privilege.DROP)
if err != nil {
return nil, err
}

// Check if any views depend on this table/view. Because our views
// are currently just stored as strings, they explicitly specify the name
// of everything they depend on. Rather than trying to rewrite the view's
// query with the new name, we simply disallow such renames for now.
if len(tableDesc.DependedOnBy) > 0 {
return nil, p.dependentViewError(
ctx, tableDesc.TypeName(), tableDesc.Name, tableDesc.ParentID, tableDesc.DependedOnBy[0].ID,
"set schema on",
)
}

return &alterTableSetSchemaNode{
newSchema: n.Schema,
tableDesc: tableDesc,
n: n,
}, nil
}

func (n *alterTableSetSchemaNode) startExec(params runParams) error {
p := params.p
ctx := params.ctx
tableDesc := n.tableDesc

if tableDesc.Temporary {
return pgerror.Newf(pgcode.FeatureNotSupported,
"cannot move objects into or out of temporary schemas")
}

databaseID := n.tableDesc.GetParentID()
schemaID := n.tableDesc.GetParentSchemaID()

// Lookup the the schema we want to set to.
exists, res, err := params.p.LogicalSchemaAccessor().GetSchema(
ctx, p.txn,
p.ExecCfg().Codec,
databaseID,
n.newSchema,
)
if err != nil {
return err
}

if !exists {
return pgerror.Newf(pgcode.InvalidSchemaName,
"schema %s does not exist", n.newSchema)
}

switch res.Kind {
case sqlbase.SchemaTemporary:
return pgerror.Newf(pgcode.FeatureNotSupported,
"cannot move objects into or out of temporary schemas")
case sqlbase.SchemaVirtual:
return pgerror.Newf(pgcode.FeatureNotSupported,
"cannot move objects into or out of virtual schemas")
case sqlbase.SchemaPublic:
// We do not need to check for privileges on the public schema.
default:
// The user needs CREATE privilege on the target schema to move a table
// to the schema.
err = p.CheckPrivilege(ctx, res.Desc, privilege.CREATE)
if err != nil {
return err
}
}

desiredSchemaID := res.ID

// If the schema being changed to is the same as the current schema for the
// table, do a no-op.
if desiredSchemaID == schemaID {
return nil
}

exists, _, err = sqlbase.LookupObjectID(
ctx, p.txn, p.ExecCfg().Codec, databaseID, desiredSchemaID, tableDesc.Name,
)
if err == nil && exists {
return pgerror.Newf(pgcode.DuplicateRelation,
"relation %s already exists in schema %s", tableDesc.Name, n.newSchema)
} else if err != nil {
return err
}

renameDetails := sqlbase.NameInfo{
ParentID: databaseID,
ParentSchemaID: schemaID,
Name: tableDesc.Name,
}
tableDesc.AddDrainingName(renameDetails)

// Set the tableDesc's new schema id to the desired schema's id.
n.tableDesc.SetParentSchemaID(desiredSchemaID)

if err := p.writeSchemaChange(
ctx, tableDesc, sqlbase.InvalidMutationID, tree.AsStringWithFQNames(n.n, params.Ann()),
); err != nil {
return err
}

newTbKey := sqlbase.MakeObjectNameKey(ctx, p.ExecCfg().Settings,
databaseID, desiredSchemaID, tableDesc.Name).Key(p.ExecCfg().Codec)

b := &kv.Batch{}
if params.p.extendedEvalCtx.Tracing.KVTracingEnabled() {
log.VEventf(ctx, 2, "CPut %s -> %d", newTbKey, tableDesc.ID)
}

b.CPut(newTbKey, tableDesc.ID, nil)

return p.txn.Run(ctx, b)
}

// ReadingOwnWrites implements the planNodeReadingOwnWrites interface.
// This is because SET SCHEMA performs multiple KV operations on descriptors
// and expects to see its own writes.
func (n *alterTableSetSchemaNode) ReadingOwnWrites() {}

func (n *alterTableSetSchemaNode) Next(runParams) (bool, error) { return false, nil }
func (n *alterTableSetSchemaNode) Values() tree.Datums { return tree.Datums{} }
func (n *alterTableSetSchemaNode) Close(context.Context) {}
Loading

0 comments on commit 655c4cb

Please sign in to comment.