diff --git a/pkg/sql/alter_schema.go b/pkg/sql/alter_schema.go index a1d00bf00ffd..49467be4a25b 100644 --- a/pkg/sql/alter_schema.go +++ b/pkg/sql/alter_schema.go @@ -17,6 +17,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "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" @@ -63,11 +64,47 @@ func (n *alterSchemaNode) startExec(params runParams) error { switch t := n.n.Cmd.(type) { case *tree.AlterSchemaRename: return params.p.renameSchema(params.ctx, n.db, n.desc, t.NewName, tree.AsStringWithFQNames(n.n, params.Ann())) + case *tree.AlterSchemaOwner: + return params.p.alterSchemaOwner(params.ctx, n.db, n.desc, t.Owner, tree.AsStringWithFQNames(n.n, params.Ann())) default: return errors.AssertionFailedf("unknown schema cmd %T", t) } } +func (p *planner) alterSchemaOwner( + ctx context.Context, + db *sqlbase.MutableDatabaseDescriptor, + scDesc *sqlbase.MutableSchemaDescriptor, + newOwner string, + jobDesc string, +) error { + privs := scDesc.GetPrivileges() + + hasOwnership, err := p.HasOwnership(ctx, scDesc) + if err != nil { + return err + } + + if err := p.checkCanAlterToNewOwner(ctx, scDesc, privs, newOwner, hasOwnership); err != nil { + return err + } + + // If the owner we want to set to is the current owner, do a no-op. + if newOwner == privs.Owner { + return nil + } + + // The new owner must also have CREATE privilege on the schema's database. + if err := p.CheckPrivilegeForUser(ctx, db, privilege.CREATE, newOwner); err != nil { + return err + } + + // Update the owner of the schema. + privs.SetOwner(newOwner) + + return p.writeSchemaDescChange(ctx, scDesc, jobDesc) +} + func (p *planner) renameSchema( ctx context.Context, db *sqlbase.MutableDatabaseDescriptor, diff --git a/pkg/sql/logictest/testdata/logic_test/alter_schema_owner b/pkg/sql/logictest/testdata/logic_test/alter_schema_owner new file mode 100644 index 000000000000..66228ba5c6e0 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/alter_schema_owner @@ -0,0 +1,81 @@ +statement ok +SET experimental_enable_user_defined_schemas = true + +statement ok +CREATE SCHEMA s + +# Ensure user must exist for set owner. +statement error pq: role/user "fake_user" does not exist +ALTER SCHEMA s OWNER TO fake_user + +# Ensure user the current user is a member of the role we're setting to. +statement error pq: must be member of role "testuser" +ALTER SCHEMA s OWNER TO testuser + +user testuser + +# Ensure the user has to be an owner alter the owner. +statement error pq: must be owner of schema s +ALTER SCHEMA s OWNER TO testuser + +user root + +statement ok +GRANT testuser TO root + +statement ok +CREATE USER testuser2 + +statement ok +GRANT testuser2 TO root + +# Ensure the desired owner has CREATE privilege on the database. +statement error pq: user testuser2 does not have CREATE privilege on database test +ALTER SCHEMA s OWNER TO testuser2 + +statement ok +GRANT CREATE ON DATABASE test TO testuser, testuser2 + +# testuser has the required privileges to become the new owner of schema s. +statement ok +ALTER SCHEMA s OWNER TO testuser + +# setup to allow testuser2 as a member of testuser to alter the owner. +statement ok +REVOKE testuser, testuser2 FROM root + +statement ok +GRANT testuser TO testuser2 + +statement ok +GRANT root TO testuser + +user testuser2 + +# testuser2 should be able to alter the owner since it is a member of testuser. +statement ok +ALTER SCHEMA s OWNER TO root + +# set the owner back to testuser. + +user root + +statement ok +REVOKE root FROM testuser + +statement ok +GRANT testuser TO root + +statement ok +ALTER SCHEMA s OWNER TO testuser + +# setup to allow testuser2 to become the owner again. +statement ok +REVOKE testuser FROM testuser2 + +statement ok +GRANT testuser2 TO testuser + +# Ensure testuser is owner by altering the owner again. +statement ok +ALTER SCHEMA s OWNER to testuser2 diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 77cdd9ae9a0d..978bc0e80611 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -5230,7 +5230,12 @@ alter_schema_stmt: } | ALTER SCHEMA schema_name OWNER TO role_spec { - return unimplementedWithIssueDetail(sqllex, 50882, "ALTER SCHEMA OWNER TO") + $$.val = &tree.AlterSchema{ + Schema: $3, + Cmd: &tree.AlterSchemaOwner{ + Owner: $6, + }, + } } | ALTER SCHEMA error // SHOW HELP: ALTER SCHEMA diff --git a/pkg/sql/sem/tree/alter_schema.go b/pkg/sql/sem/tree/alter_schema.go index 1904845d3db9..bf0b2994afa6 100644 --- a/pkg/sql/sem/tree/alter_schema.go +++ b/pkg/sql/sem/tree/alter_schema.go @@ -43,3 +43,16 @@ func (node *AlterSchemaRename) Format(ctx *FmtCtx) { ctx.WriteString(" RENAME TO ") ctx.FormatNameP(&node.NewName) } + +func (*AlterSchemaOwner) alterSchemaCmd() {} + +// AlterSchemaOwner represents an ALTER SCHEMA RENAME command. +type AlterSchemaOwner struct { + Owner string +} + +// Format implements the NodeFormatter interface. +func (node *AlterSchemaOwner) Format(ctx *FmtCtx) { + ctx.WriteString(" OWNER TO ") + ctx.FormatNameP(&node.Owner) +}