Skip to content

Commit 3c96091

Browse files
committed
[SPARK-30018][SQL] Support ALTER DATABASE SET OWNER syntax
1 parent 755d889 commit 3c96091

File tree

9 files changed

+111
-23
lines changed

9 files changed

+111
-23
lines changed

sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBase.g4

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ statement
9797
SET (DBPROPERTIES | PROPERTIES) tablePropertyList #setNamespaceProperties
9898
| ALTER (database | NAMESPACE) multipartIdentifier
9999
SET locationSpec #setNamespaceLocation
100+
| ALTER (database | NAMESPACE) multipartIdentifier
101+
SET OWNER ownerType=(USER | ROLE | GROUP) IDENTIFIER #setNamespaceOwner
100102
| DROP (database | NAMESPACE) (IF EXISTS)? multipartIdentifier
101103
(RESTRICT | CASCADE)? #dropNamespace
102104
| SHOW (DATABASES | NAMESPACES) ((FROM | IN) multipartIdentifier)?
@@ -1355,6 +1357,7 @@ nonReserved
13551357
| OVERLAPS
13561358
| OVERLAY
13571359
| OVERWRITE
1360+
| OWNER
13581361
| PARTITION
13591362
| PARTITIONED
13601363
| PARTITIONS
@@ -1623,6 +1626,7 @@ OVER: 'OVER';
16231626
OVERLAPS: 'OVERLAPS';
16241627
OVERLAY: 'OVERLAY';
16251628
OVERWRITE: 'OVERWRITE';
1629+
OWNER: 'OWNER';
16261630
PARTITION: 'PARTITION';
16271631
PARTITIONED: 'PARTITIONED';
16281632
PARTITIONS: 'PARTITIONS';

sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/SupportsNamespaces.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public interface SupportsNamespaces extends CatalogPlugin {
6666
/**
6767
* The list of reserved namespace properties.
6868
*/
69-
List<String> RESERVED_PROPERTIES = Arrays.asList(PROP_COMMENT, PROP_LOCATION);
69+
List<String> RESERVED_PROPERTIES =
70+
Arrays.asList(PROP_COMMENT, PROP_LOCATION, PROP_OWNER_NAME, PROP_OWNER_TYPE);
7071

7172
/**
7273
* Return a default namespace for the catalog.

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveCatalogs.scala

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717

1818
package org.apache.spark.sql.catalyst.analysis
1919

20+
import scala.collection.JavaConverters._
21+
2022
import org.apache.spark.sql.AnalysisException
2123
import org.apache.spark.sql.catalyst.plans.logical._
2224
import org.apache.spark.sql.catalyst.rules.Rule
23-
import org.apache.spark.sql.connector.catalog.{CatalogManager, CatalogPlugin, LookupCatalog, SupportsNamespaces, TableCatalog, TableChange}
25+
import org.apache.spark.sql.connector.catalog.{CatalogManager, CatalogPlugin, LookupCatalog, TableCatalog, TableChange}
26+
import org.apache.spark.sql.connector.catalog.SupportsNamespaces._
2427

2528
/**
2629
* Resolves catalogs from the multi-part identifiers in SQL statements, and convert the statements
@@ -94,11 +97,18 @@ class ResolveCatalogs(val catalogManager: CatalogManager)
9497
s"because view support in catalog has not been implemented yet")
9598

9699
case AlterNamespaceSetPropertiesStatement(NonSessionCatalog(catalog, nameParts), properties) =>
97-
AlterNamespaceSetProperties(catalog.asNamespaceCatalog, nameParts, properties)
100+
val availableProps = properties -- RESERVED_PROPERTIES.asScala
101+
AlterNamespaceSetProperties(catalog.asNamespaceCatalog, nameParts, availableProps)
98102

99103
case AlterNamespaceSetLocationStatement(NonSessionCatalog(catalog, nameParts), location) =>
100104
AlterNamespaceSetProperties(catalog.asNamespaceCatalog, nameParts,
101-
Map(SupportsNamespaces.PROP_LOCATION -> location))
105+
Map(PROP_LOCATION -> location))
106+
107+
case AlterNamespaceSetOwnerStatement(NonSessionCatalog(catalog, nameParts), name, typ) =>
108+
AlterNamespaceSetProperties(
109+
catalog.asNamespaceCatalog,
110+
nameParts,
111+
Map(PROP_OWNER_NAME -> name, PROP_OWNER_TYPE -> typ))
102112

103113
case RenameTableStatement(NonSessionCatalog(catalog, oldName), newNameParts, isView) =>
104114
if (isView) {

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2579,6 +2579,23 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
25792579
}
25802580
}
25812581

2582+
/**
2583+
* Create an [[AlterNamespaceSetOwnerStatement]] logical plan.
2584+
*
2585+
* For example:
2586+
* {{{
2587+
* ALTER (DATABASE|SCHEMA|NAMESPACE) namespace SET OWNER (USER|ROLE|GROUP) identityName;
2588+
* }}}
2589+
*/
2590+
override def visitSetNamespaceOwner(ctx: SetNamespaceOwnerContext): LogicalPlan = {
2591+
withOrigin(ctx) {
2592+
AlterNamespaceSetOwnerStatement(
2593+
visitMultipartIdentifier(ctx.multipartIdentifier),
2594+
ctx.IDENTIFIER.getText,
2595+
ctx.ownerType.getText)
2596+
}
2597+
}
2598+
25822599
/**
25832600
* Create a [[ShowNamespacesStatement]] command.
25842601
*/

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/statements.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ case class AlterNamespaceSetLocationStatement(
382382
namespace: Seq[String],
383383
location: String) extends ParsedStatement
384384

385+
/**
386+
* ALTER (DATABASE|SCHEMA|NAMESPACE) ... SET OWNER command, as parsed from SQL.
387+
*/
388+
case class AlterNamespaceSetOwnerStatement(
389+
namespace: Seq[String],
390+
ownerName: String,
391+
ownerType: String) extends ParsedStatement
392+
385393
/**
386394
* A SHOW NAMESPACES statement, as parsed from SQL.
387395
*/

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DDLParserSuite.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,19 @@ class DDLParserSuite extends AnalysisTest {
12151215
AlterNamespaceSetLocationStatement(Seq("a", "b", "c"), "/home/user/db"))
12161216
}
12171217

1218+
test("set namespace owner") {
1219+
comparePlans(
1220+
parsePlan("ALTER DATABASE a.b.c SET OWNER USER user1"),
1221+
AlterNamespaceSetOwnerStatement(Seq("a", "b", "c"), "user1", "USER"))
1222+
1223+
comparePlans(
1224+
parsePlan("ALTER DATABASE a.b.c SET OWNER ROLE role1"),
1225+
AlterNamespaceSetOwnerStatement(Seq("a", "b", "c"), "role1", "ROLE"))
1226+
comparePlans(
1227+
parsePlan("ALTER DATABASE a.b.c SET OWNER GROUP group1"),
1228+
AlterNamespaceSetOwnerStatement(Seq("a", "b", "c"), "group1", "GROUP"))
1229+
}
1230+
12181231
test("show databases: basic") {
12191232
comparePlans(
12201233
parsePlan("SHOW DATABASES"),

sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ class ResolveSessionCatalog(
181181
}
182182
AlterDatabaseSetLocationCommand(nameParts.head, location)
183183

184+
case AlterNamespaceSetOwnerStatement(SessionCatalog(_, nameParts), ownerName, ownerType) =>
185+
if (nameParts.length != 1) {
186+
throw new AnalysisException(
187+
s"The database name is not valid: ${nameParts.quoted}")
188+
}
189+
AlterDatabaseSetOwnerCommand(nameParts.head, ownerName, ownerType)
190+
184191
case RenameTableStatement(SessionCatalog(_, oldName), newNameParts, isView) =>
185192
AlterTableRenameCommand(oldName.asTableIdentifier, newNameParts.asTableIdentifier, isView)
186193

sql/core/src/main/scala/org/apache/spark/sql/execution/command/ddl.scala

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,13 @@ case class CreateDatabaseCommand(
7272

7373
override def run(sparkSession: SparkSession): Seq[Row] = {
7474
val catalog = sparkSession.sessionState.catalog
75+
val availablePros = props -- RESERVED_PROPERTIES.asScala
7576
catalog.createDatabase(
7677
CatalogDatabase(
7778
databaseName,
7879
comment.getOrElse(""),
7980
path.map(CatalogUtils.stringToURI).getOrElse(catalog.getDefaultDBPath(databaseName)),
80-
props),
81+
availablePros),
8182
ifNotExists)
8283
Seq.empty[Row]
8384
}
@@ -129,7 +130,8 @@ case class AlterDatabasePropertiesCommand(
129130
override def run(sparkSession: SparkSession): Seq[Row] = {
130131
val catalog = sparkSession.sessionState.catalog
131132
val db: CatalogDatabase = catalog.getDatabaseMetadata(databaseName)
132-
catalog.alterDatabase(db.copy(properties = db.properties ++ props))
133+
val availableProps = props -- RESERVED_PROPERTIES.asScala
134+
catalog.alterDatabase(db.copy(properties = db.properties ++ availableProps))
133135

134136
Seq.empty[Row]
135137
}
@@ -156,6 +158,26 @@ case class AlterDatabaseSetLocationCommand(databaseName: String, location: Strin
156158
}
157159
}
158160

161+
/**
162+
* A command for users to set ownership for a database
163+
* If the database does not exist, an error message will be issued to indicate the database
164+
* does not exist.
165+
* The syntax of using this command in SQL is:
166+
* {{{
167+
* ALTER (DATABASE|SCHEMA) database_name SET OWNER [USER|ROLE|GROUP] identityName
168+
* }}}
169+
*/
170+
case class AlterDatabaseSetOwnerCommand(databaseName: String, ownerName: String, ownerType: String)
171+
extends RunnableCommand {
172+
override def run(sparkSession: SparkSession): Seq[Row] = {
173+
val catalog = sparkSession.sessionState.catalog
174+
val database = catalog.getDatabaseMetadata(databaseName)
175+
val ownerships = Map(PROP_OWNER_NAME -> ownerName, PROP_OWNER_TYPE -> ownerType)
176+
catalog.alterDatabase(database.copy(properties = database.properties ++ ownerships))
177+
Seq.empty[Row]
178+
}
179+
}
180+
159181
/**
160182
* A command for users to show the name of the database, its comment (if one has been set), and its
161183
* root location on the filesystem. When extended is true, it also shows the database's properties
@@ -183,7 +205,7 @@ case class DescribeDatabaseCommand(
183205
Row("Owner Type", allDbProperties.getOrElse(PROP_OWNER_TYPE, "")) :: Nil
184206

185207
if (extended) {
186-
val properties = allDbProperties -- Seq(PROP_OWNER_NAME, PROP_OWNER_TYPE)
208+
val properties = allDbProperties -- RESERVED_PROPERTIES.asScala
187209
val propertiesStr =
188210
if (properties.isEmpty) {
189211
""

sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/HiveDDLSuite.scala

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,14 @@ class HiveCatalogedDDLSuite extends DDLSuite with TestHiveSingleton with BeforeA
374374
}
375375
}
376376

377-
private def checkOwner(db: String, expected: String): Unit = {
378-
val owner = sql(s"DESCRIBE DATABASE EXTENDED $db")
379-
.where("database_description_item='Owner Name'")
377+
private def checkOwner(db: String, expectedOwnerName: String, expectedOwnerType: String): Unit = {
378+
val df = sql(s"DESCRIBE DATABASE EXTENDED $db")
379+
val owner = df.where("database_description_item='Owner Name'")
380380
.collect().head.getString(1)
381-
assert(owner === expected)
381+
val typ = df.where("database_description_item='Owner Type'")
382+
.collect().head.getString(1)
383+
assert(owner === expectedOwnerName)
384+
assert(typ === expectedOwnerType)
382385
}
383386

384387
test("Database Ownership") {
@@ -387,20 +390,23 @@ class HiveCatalogedDDLSuite extends DDLSuite with TestHiveSingleton with BeforeA
387390
val db1 = "spark_29425_1"
388391
val db2 = "spark_29425_2"
389392
val owner = "spark_29425"
393+
val currentUser = Utils.getCurrentUserName()
390394

391395
sql(s"CREATE DATABASE $db1")
392-
checkOwner(db1, Utils.getCurrentUserName())
393-
sql(s"ALTER DATABASE $db1 SET DBPROPERTIES ('a'='a')")
394-
checkOwner(db1, Utils.getCurrentUserName())
395-
396-
// TODO: Specify ownership should be forbidden after we implement `SET OWNER` syntax
397-
sql(s"CREATE DATABASE $db2 WITH DBPROPERTIES('ownerName'='$owner')")
398-
checkOwner(db2, owner)
399-
sql(s"ALTER DATABASE $db2 SET DBPROPERTIES ('a'='a')")
400-
checkOwner(db2, owner)
401-
// TODO: Changing ownership should be forbidden after we implement `SET OWNER` syntax
402-
sql(s"ALTER DATABASE $db2 SET DBPROPERTIES ('ownerName'='a')")
403-
checkOwner(db2, "a")
396+
checkOwner(db1, currentUser, "USER")
397+
sql(s"ALTER DATABASE $db1 SET DBPROPERTIES ('a'='a', 'ownerName'='$owner'," +
398+
s" 'ownerType'='XXX')")
399+
checkOwner(db1, currentUser, "USER")
400+
sql(s"ALTER DATABASE $db1 SET OWNER ROLE $owner")
401+
checkOwner(db1, owner, "ROLE")
402+
403+
sql(s"CREATE DATABASE $db2 WITH DBPROPERTIES('ownerName'='$owner', 'ownerType'='XXX')")
404+
checkOwner(db2, currentUser, "USER")
405+
sql(s"ALTER DATABASE $db2 SET DBPROPERTIES ('a'='a', 'ownerName'='$owner'," +
406+
s" 'ownerType'='XXX')")
407+
checkOwner(db2, currentUser, "USER")
408+
sql(s"ALTER DATABASE $db2 SET OWNER GROUP $owner")
409+
checkOwner(db2, owner, "GROUP")
404410
} finally {
405411
catalog.reset()
406412
}

0 commit comments

Comments
 (0)