Skip to content

Commit 414771d

Browse files
imback82cloud-fan
authored andcommitted
[SPARK-37590][SQL][TEST] Unify v1 and v2 ALTER TABLE .. SET PROPERTIES tests
### What changes were proposed in this pull request? 1. Move `ALTER NAMESPACE ... SET PROPERTIES` parsing tests to `AlterNamespaceSetPropertiesParserSuite`. 2. Put common `ALTER NAMESPACE ... SET PROPERTIES` tests into one trait `org.apache.spark.sql.execution.command.AlterNamespaceSetPropertiesSuiteBase`, and put datasource specific tests to the `v1.AlterNamespaceSetPropertiesSuite` and `v2.AlterNamespaceSetPropertiesSuite`. The changes follow the approach of #30287. ### Why are the changes needed? 1. The unification will allow to run common `ALTER NAMESPACE ... SET PROPERTIES` tests for both DSv1/Hive DSv1 and DSv2 2. We can detect missing features and differences between DSv1 and DSv2 implementations. ### Does this PR introduce _any_ user-facing change? No ### How was this patch tested? Existing unit tests and new tests. Closes #34842 from imback82/alter_namespace_set_properties_tests. Authored-by: Terry Kim <yuminkim@gmail.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
1 parent 1cc1459 commit 414771d

File tree

10 files changed

+277
-115
lines changed

10 files changed

+277
-115
lines changed

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/InMemoryCatalog.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import org.apache.spark.sql.catalyst.analysis._
3030
import org.apache.spark.sql.catalyst.catalog.ExternalCatalogUtils._
3131
import org.apache.spark.sql.catalyst.expressions.Expression
3232
import org.apache.spark.sql.catalyst.util.StringUtils
33+
import org.apache.spark.sql.connector.catalog.SupportsNamespaces.PROP_OWNER
3334
import org.apache.spark.sql.errors.{QueryCompilationErrors, QueryExecutionErrors}
3435
import org.apache.spark.sql.types.StructType
36+
import org.apache.spark.util.Utils
3537

3638
/**
3739
* An in-memory (ephemeral) implementation of the system catalog.
@@ -124,7 +126,9 @@ class InMemoryCatalog(
124126
throw QueryExecutionErrors.unableToCreateDatabaseAsFailedToCreateDirectoryError(
125127
dbDefinition, e)
126128
}
127-
catalog.put(dbDefinition.name, new DatabaseDesc(dbDefinition))
129+
val newDb = dbDefinition.copy(
130+
properties = dbDefinition.properties ++ Map(PROP_OWNER -> Utils.getCurrentUserName))
131+
catalog.put(dbDefinition.name, new DatabaseDesc(newDb))
128132
}
129133
}
130134

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

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,38 +1770,6 @@ class DDLParserSuite extends AnalysisTest {
17701770
"location" -> "/home/user/db")))
17711771
}
17721772

1773-
test("set namespace properties") {
1774-
comparePlans(
1775-
parsePlan("ALTER DATABASE a.b.c SET PROPERTIES ('a'='a', 'b'='b', 'c'='c')"),
1776-
SetNamespaceProperties(
1777-
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a", "b" -> "b", "c" -> "c")))
1778-
1779-
comparePlans(
1780-
parsePlan("ALTER SCHEMA a.b.c SET PROPERTIES ('a'='a')"),
1781-
SetNamespaceProperties(
1782-
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a")))
1783-
1784-
comparePlans(
1785-
parsePlan("ALTER NAMESPACE a.b.c SET PROPERTIES ('b'='b')"),
1786-
SetNamespaceProperties(
1787-
UnresolvedNamespace(Seq("a", "b", "c")), Map("b" -> "b")))
1788-
1789-
comparePlans(
1790-
parsePlan("ALTER DATABASE a.b.c SET DBPROPERTIES ('a'='a', 'b'='b', 'c'='c')"),
1791-
SetNamespaceProperties(
1792-
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a", "b" -> "b", "c" -> "c")))
1793-
1794-
comparePlans(
1795-
parsePlan("ALTER SCHEMA a.b.c SET DBPROPERTIES ('a'='a')"),
1796-
SetNamespaceProperties(
1797-
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a")))
1798-
1799-
comparePlans(
1800-
parsePlan("ALTER NAMESPACE a.b.c SET DBPROPERTIES ('b'='b')"),
1801-
SetNamespaceProperties(
1802-
UnresolvedNamespace(Seq("a", "b", "c")), Map("b" -> "b")))
1803-
}
1804-
18051773
test("analyze table statistics") {
18061774
comparePlans(parsePlan("analyze table a.b.c compute statistics"),
18071775
AnalyzeTable(

sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,54 +1262,6 @@ class DataSourceV2SQLSuite
12621262
}
12631263
}
12641264

1265-
test("ALTER NAMESPACE .. SET PROPERTIES using v2 catalog") {
1266-
withNamespace("testcat.ns1.ns2") {
1267-
sql("CREATE NAMESPACE IF NOT EXISTS testcat.ns1.ns2 COMMENT " +
1268-
"'test namespace' LOCATION '/tmp/ns_test' WITH PROPERTIES ('a'='a','b'='b','c'='c')")
1269-
sql("ALTER NAMESPACE testcat.ns1.ns2 SET PROPERTIES ('a'='b','b'='a')")
1270-
val descriptionDf = sql("DESCRIBE NAMESPACE EXTENDED testcat.ns1.ns2")
1271-
assert(descriptionDf.collect() === Seq(
1272-
Row("Namespace Name", "ns2"),
1273-
Row(SupportsNamespaces.PROP_COMMENT.capitalize, "test namespace"),
1274-
Row(SupportsNamespaces.PROP_LOCATION.capitalize, "file:/tmp/ns_test"),
1275-
Row(SupportsNamespaces.PROP_OWNER.capitalize, defaultUser),
1276-
Row("Properties", "((a,b), (b,a), (c,c))"))
1277-
)
1278-
}
1279-
}
1280-
1281-
test("ALTER NAMESPACE .. SET PROPERTIES reserved properties") {
1282-
import SupportsNamespaces._
1283-
withSQLConf((SQLConf.LEGACY_PROPERTY_NON_RESERVED.key, "false")) {
1284-
CatalogV2Util.NAMESPACE_RESERVED_PROPERTIES.filterNot(_ == PROP_COMMENT).foreach { key =>
1285-
withNamespace("testcat.reservedTest") {
1286-
sql("CREATE NAMESPACE testcat.reservedTest")
1287-
val exception = intercept[ParseException] {
1288-
sql(s"ALTER NAMESPACE testcat.reservedTest SET PROPERTIES ('$key'='dummyVal')")
1289-
}
1290-
assert(exception.getMessage.contains(s"$key is a reserved namespace property"))
1291-
}
1292-
}
1293-
}
1294-
withSQLConf((SQLConf.LEGACY_PROPERTY_NON_RESERVED.key, "true")) {
1295-
CatalogV2Util.NAMESPACE_RESERVED_PROPERTIES.filterNot(_ == PROP_COMMENT).foreach { key =>
1296-
withNamespace("testcat.reservedTest") {
1297-
sql(s"CREATE NAMESPACE testcat.reservedTest")
1298-
sql(s"ALTER NAMESPACE testcat.reservedTest SET PROPERTIES ('$key'='foo')")
1299-
assert(sql("DESC NAMESPACE EXTENDED testcat.reservedTest")
1300-
.toDF("k", "v")
1301-
.where("k='Properties'")
1302-
.where("v=''")
1303-
.count == 1, s"$key is a reserved namespace property and ignored")
1304-
val meta =
1305-
catalog("testcat").asNamespaceCatalog.loadNamespaceMetadata(Array("reservedTest"))
1306-
assert(meta.get(key) == null || !meta.get(key).contains("foo"),
1307-
"reserved properties should not have side effects")
1308-
}
1309-
}
1310-
}
1311-
}
1312-
13131265
private def testShowNamespaces(
13141266
sqlText: String,
13151267
expected: Seq[String]): Unit = {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.execution.command
19+
20+
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedNamespace}
21+
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
22+
import org.apache.spark.sql.catalyst.parser.ParseException
23+
import org.apache.spark.sql.catalyst.plans.logical.SetNamespaceProperties
24+
25+
class AlterNamespaceSetPropertiesParserSuite extends AnalysisTest {
26+
test("set namespace properties") {
27+
Seq("DATABASE", "SCHEMA", "NAMESPACE").foreach { nsToken =>
28+
Seq("PROPERTIES", "DBPROPERTIES").foreach { propToken =>
29+
comparePlans(
30+
parsePlan(s"ALTER $nsToken a.b.c SET $propToken ('a'='a', 'b'='b', 'c'='c')"),
31+
SetNamespaceProperties(
32+
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a", "b" -> "b", "c" -> "c")))
33+
34+
comparePlans(
35+
parsePlan(s"ALTER $nsToken a.b.c SET $propToken ('a'='a')"),
36+
SetNamespaceProperties(
37+
UnresolvedNamespace(Seq("a", "b", "c")), Map("a" -> "a")))
38+
}
39+
}
40+
}
41+
42+
test("property values must be set") {
43+
val e = intercept[ParseException] {
44+
parsePlan("ALTER NAMESPACE my_db SET PROPERTIES('key_without_value', 'key_with_value'='x')")
45+
}
46+
assert(e.getMessage.contains(
47+
"Operation not allowed: Values must be specified for key(s): [key_without_value]"))
48+
}
49+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.execution.command
19+
20+
import org.apache.spark.sql.{AnalysisException, QueryTest}
21+
import org.apache.spark.sql.catalyst.parser.ParseException
22+
import org.apache.spark.sql.connector.catalog.{CatalogV2Util, SupportsNamespaces}
23+
import org.apache.spark.sql.internal.SQLConf
24+
25+
/**
26+
* This base suite contains unified tests for the `ALTER NAMESPACE ... SET PROPERTIES` command that
27+
* check V1 and V2 table catalogs. The tests that cannot run for all supported catalogs are located
28+
* in more specific test suites:
29+
*
30+
* - V2 table catalog tests:
31+
* `org.apache.spark.sql.execution.command.v2.AlterNamespaceSetPropertiesSuite`
32+
* - V1 table catalog tests:
33+
* `org.apache.spark.sql.execution.command.v1.AlterNamespaceSetPropertiesSuiteBase`
34+
* - V1 In-Memory catalog:
35+
* `org.apache.spark.sql.execution.command.v1.AlterNamespaceSetPropertiesSuite`
36+
* - V1 Hive External catalog:
37+
* `org.apache.spark.sql.hive.execution.command.AlterNamespaceSetPropertiesSuite`
38+
*/
39+
trait AlterNamespaceSetPropertiesSuiteBase extends QueryTest with DDLCommandTestUtils {
40+
override val command = "ALTER NAMESPACE ... SET PROPERTIES"
41+
42+
protected def namespace: String
43+
44+
protected def notFoundMsgPrefix: String
45+
46+
test("Namespace does not exist") {
47+
val ns = "not_exist"
48+
val message = intercept[AnalysisException] {
49+
sql(s"ALTER DATABASE $catalog.$ns SET PROPERTIES ('d'='d')")
50+
}.getMessage
51+
assert(message.contains(s"$notFoundMsgPrefix '$ns' not found"))
52+
}
53+
54+
test("basic test") {
55+
val ns = s"$catalog.$namespace"
56+
withNamespace(ns) {
57+
sql(s"CREATE NAMESPACE $ns")
58+
assert(getProperties(ns) === "")
59+
sql(s"ALTER NAMESPACE $ns SET PROPERTIES ('a'='a', 'b'='b', 'c'='c')")
60+
assert(getProperties(ns) === "((a,a), (b,b), (c,c))")
61+
sql(s"ALTER DATABASE $ns SET PROPERTIES ('d'='d')")
62+
assert(getProperties(ns) === "((a,a), (b,b), (c,c), (d,d))")
63+
}
64+
}
65+
66+
test("test with properties set while creating namespace") {
67+
val ns = s"$catalog.$namespace"
68+
withNamespace(ns) {
69+
sql(s"CREATE NAMESPACE $ns WITH PROPERTIES ('a'='a','b'='b','c'='c')")
70+
assert(getProperties(ns) === "((a,a), (b,b), (c,c))")
71+
sql(s"ALTER NAMESPACE $ns SET PROPERTIES ('a'='b', 'b'='a')")
72+
assert(getProperties(ns) === "((a,b), (b,a), (c,c))")
73+
}
74+
}
75+
76+
test("test reserved properties") {
77+
import SupportsNamespaces._
78+
import org.apache.spark.sql.connector.catalog.CatalogV2Implicits._
79+
val ns = s"$catalog.$namespace"
80+
withSQLConf((SQLConf.LEGACY_PROPERTY_NON_RESERVED.key, "false")) {
81+
CatalogV2Util.NAMESPACE_RESERVED_PROPERTIES.filterNot(_ == PROP_COMMENT).foreach { key =>
82+
withNamespace(ns) {
83+
sql(s"CREATE NAMESPACE $ns")
84+
val exception = intercept[ParseException] {
85+
sql(s"ALTER NAMESPACE $ns SET PROPERTIES ('$key'='dummyVal')")
86+
}
87+
assert(exception.getMessage.contains(s"$key is a reserved namespace property"))
88+
}
89+
}
90+
}
91+
withSQLConf((SQLConf.LEGACY_PROPERTY_NON_RESERVED.key, "true")) {
92+
CatalogV2Util.NAMESPACE_RESERVED_PROPERTIES.filterNot(_ == PROP_COMMENT).foreach { key =>
93+
withNamespace(ns) {
94+
// Set the location explicitly because v2 catalog may not set the default location.
95+
// Without this, `meta.get(key)` below may return null.
96+
sql(s"CREATE NAMESPACE $ns LOCATION '/tmp'")
97+
assert(getProperties(ns) === "")
98+
sql(s"ALTER NAMESPACE $ns SET PROPERTIES ('$key'='foo')")
99+
assert(getProperties(ns) === "", s"$key is a reserved namespace property and ignored")
100+
val meta = spark.sessionState.catalogManager.catalog(catalog)
101+
.asNamespaceCatalog.loadNamespaceMetadata(namespace.split('.'))
102+
assert(!meta.get(key).contains("foo"),
103+
"reserved properties should not have side effects")
104+
}
105+
}
106+
}
107+
}
108+
109+
protected def getProperties(namespace: String): String = {
110+
val propsRow = sql(s"DESCRIBE NAMESPACE EXTENDED $namespace")
111+
.toDF("key", "value")
112+
.where(s"key like 'Properties%'")
113+
.collect()
114+
assert(propsRow.length == 1)
115+
propsRow(0).getString(1)
116+
}
117+
}

sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLParserSuite.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ class DDLParserSuite extends AnalysisTest with SharedSparkSession {
5959
ShowCurrentNamespaceCommand())
6060
}
6161

62-
test("alter database - property values must be set") {
63-
assertUnsupported(
64-
sql = "ALTER DATABASE my_db SET DBPROPERTIES('key_without_value', 'key_with_value'='x')",
65-
containsThesePhrases = Seq("key_without_value"))
66-
}
67-
6862
test("insert overwrite directory") {
6963
val v1 = "INSERT OVERWRITE DIRECTORY '/tmp/file' USING parquet SELECT 1 as a"
7064
parser.parsePlan(v1) match {

sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLSuite.scala

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ abstract class DDLSuite extends QueryTest with SQLTestUtils {
751751
}
752752
}
753753

754-
test("Alter/Describe Database") {
754+
test("Describe Database") {
755755
val catalog = spark.sessionState.catalog
756756
val databaseNames = Seq("db1", "`database`")
757757

@@ -760,9 +760,7 @@ abstract class DDLSuite extends QueryTest with SQLTestUtils {
760760
val dbNameWithoutBackTicks = cleanIdentifier(dbName)
761761
val location = getDBPath(dbNameWithoutBackTicks)
762762

763-
sql(s"CREATE DATABASE $dbName")
764-
765-
sql(s"ALTER DATABASE $dbName SET DBPROPERTIES ('a'='a', 'b'='b', 'c'='c')")
763+
sql(s"CREATE DATABASE $dbName WITH PROPERTIES ('a'='a', 'b'='b', 'c'='c')")
766764

767765
checkAnswer(
768766
sql(s"DESCRIBE DATABASE EXTENDED $dbName").toDF("key", "value")
@@ -771,36 +769,12 @@ abstract class DDLSuite extends QueryTest with SQLTestUtils {
771769
Row("Comment", "") ::
772770
Row("Location", CatalogUtils.URIToString(location)) ::
773771
Row("Properties", "((a,a), (b,b), (c,c))") :: Nil)
774-
775-
sql(s"ALTER DATABASE $dbName SET DBPROPERTIES ('d'='d')")
776-
777-
checkAnswer(
778-
sql(s"DESCRIBE DATABASE EXTENDED $dbName").toDF("key", "value")
779-
.where("key not like 'Owner%'"), // filter for consistency with in-memory catalog
780-
Row("Namespace Name", dbNameWithoutBackTicks) ::
781-
Row("Comment", "") ::
782-
Row("Location", CatalogUtils.URIToString(location)) ::
783-
Row("Properties", "((a,a), (b,b), (c,c), (d,d))") :: Nil)
784772
} finally {
785773
catalog.reset()
786774
}
787775
}
788776
}
789777

790-
test("Alter Database - database does not exists") {
791-
val databaseNames = Seq("db1", "`database`")
792-
793-
databaseNames.foreach { dbName =>
794-
val dbNameWithoutBackTicks = cleanIdentifier(dbName)
795-
assert(!spark.sessionState.catalog.databaseExists(dbNameWithoutBackTicks))
796-
797-
val message = intercept[AnalysisException] {
798-
sql(s"ALTER DATABASE $dbName SET DBPROPERTIES ('d'='d')")
799-
}.getMessage
800-
assert(message.contains(s"Database '$dbNameWithoutBackTicks' not found"))
801-
}
802-
}
803-
804778
test("create table in default db") {
805779
val catalog = spark.sessionState.catalog
806780
val tableIdent1 = TableIdentifier("tab1", None)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.execution.command.v1
19+
20+
import org.apache.spark.sql.execution.command
21+
22+
/**
23+
* This base suite contains unified tests for the `ALTER NAMESPACE ... SET PROPERTIES` command that
24+
* checks V1 table catalogs. The tests that cannot run for all V1 catalogs are located in more
25+
* specific test suites:
26+
*
27+
* - V1 In-Memory catalog:
28+
* `org.apache.spark.sql.execution.command.v1.AlterNamespaceSetPropertiesSuite`
29+
* - V1 Hive External catalog:
30+
* `org.apache.spark.sql.hive.execution.command.AlterNamespaceSetPropertiesSuite`
31+
*/
32+
trait AlterNamespaceSetPropertiesSuiteBase extends command.AlterNamespaceSetPropertiesSuiteBase
33+
with command.TestsV1AndV2Commands {
34+
override def namespace: String = "db"
35+
override def notFoundMsgPrefix: String = "Database"
36+
}
37+
38+
/**
39+
* The class contains tests for the `ALTER NAMESPACE ... SET PROPERTIES` command to
40+
* check V1 In-Memory table catalog.
41+
*/
42+
class AlterNamespaceSetPropertiesSuite extends AlterNamespaceSetPropertiesSuiteBase
43+
with CommandSuiteBase {
44+
override def commandVersion: String = super[AlterNamespaceSetPropertiesSuiteBase].commandVersion
45+
}

0 commit comments

Comments
 (0)