This is an automated email from the ASF dual-hosted git repository.

cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 72edddb358e3 [SPARK-57295][SQL] Make database location validation 
consistent for w…
72edddb358e3 is described below

commit 72edddb358e3524db787002a12fc75420f8d1943
Author: Anurag Kumar Dwivedi <[email protected]>
AuthorDate: Thu Jun 11 12:58:56 2026 -0700

    [SPARK-57295][SQL] Make database location validation consistent for w…
    
    **PR Description**
    What changes were proposed in this pull request?
    This PR updates database location validation to reject whitespace-only 
location values during namespace/database creation and location alteration.
    
    Currently Spark rejects only empty string locations:
    `CREATE DATABASE db LOCATION ''`
    with:
    `INVALID_EMPTY_LOCATION`
    
    However, whitespace-only values such as:
    
    ```
    CREATE DATABASE db LOCATION ' '
    CREATE DATABASE db LOCATION '\t'
    CREATE DATABASE db LOCATION '\n'
    ```
    are not rejected by Spark validation.
    
    As a result:
    - In non-HMS catalog paths, databases may be created successfully using 
whitespace-only locations.
    - In HMS-backed catalog paths, the request reaches Hive Metastore and fails 
later with metastore-specific exceptions.
    
    This PR trims the location value before performing the empty-location 
validation so that whitespace-only values are treated consistently as empty 
locations.
    
    The validation is updated in:
    ```
    ResolveSessionCatalog
    DataSourceV2Strategy
    ```
    Additional regression tests are added for:
    ```
    ' '
    '\t'
    '\n'
    ```
    for both:
    ```
    namespace/database creation
    namespace/database location alteration
    ```
    
    By treating whitespace-only values as empty locations, Spark provides:
    - consistent validation behavior
    - consistent error reporting
    - earlier failure during analysis
    - reduced dependence on catalog-specific validation
    
    **Does this PR introduce any user-facing change?**
    Yes.
    
    The following statements will now fail consistently with:
    `INVALID_EMPTY_LOCATION`
    
    **Examples:**
    ```
    CREATE DATABASE db LOCATION ' '
    CREATE DATABASE db LOCATION '\t'
    CREATE DATABASE db LOCATION '\n'
    ```
    
    Jira - https://issues.apache.org/jira/browse/SPARK-57295
    
    Closes #56356 from AnuragKDwivedi/SPARK-57295-db-location-validation.
    
    Authored-by: Anurag Kumar Dwivedi <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit 9357bc9ae05e3848995df2f2f68bb2fa0759e826)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../sql/catalyst/analysis/ResolveSessionCatalog.scala    |  4 ++--
 .../execution/datasources/v2/DataSourceV2Strategy.scala  |  4 ++--
 .../command/AlterNamespaceSetLocationSuiteBase.scala     | 16 +++++++++-------
 .../sql/execution/command/CreateNamespaceSuiteBase.scala | 16 +++++++++-------
 4 files changed, 22 insertions(+), 18 deletions(-)

diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
index cfd52707bbc2..b7fa1f422364 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveSessionCatalog.scala
@@ -187,7 +187,7 @@ class ResolveSessionCatalog(val catalogManager: 
CatalogManager)
       AlterDatabasePropertiesCommand(db, properties)
 
     case SetNamespaceLocation(ResolvedV1Database(db), location) if 
conf.useV1Command =>
-      if (SparkStringUtils.isEmpty(location)) {
+      if (SparkStringUtils.isBlank(location)) {
         throw QueryExecutionErrors.invalidEmptyLocationError(location)
       }
       AlterDatabaseSetLocationCommand(db, location)
@@ -359,7 +359,7 @@ class ResolveSessionCatalog(val catalogManager: 
CatalogManager)
       val comment = c.properties.get(SupportsNamespaces.PROP_COMMENT)
       val location = c.properties.get(SupportsNamespaces.PROP_LOCATION)
       val newProperties = c.properties -- 
CatalogV2Util.NAMESPACE_RESERVED_PROPERTIES
-      if (location.isDefined && location.get.isEmpty) {
+      if (location.exists(SparkStringUtils.isBlank)) {
         throw QueryExecutionErrors.invalidEmptyLocationError(location.get)
       }
       CreateDatabaseCommand(name, c.ifNotExists, location, comment, 
newProperties)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
index 8e40f01601d5..c6661488cae5 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala
@@ -635,7 +635,7 @@ class DataSourceV2Strategy(session: SparkSession) extends 
Strategy with Predicat
       AlterNamespaceSetPropertiesExec(catalog.asNamespaceCatalog, ns, 
properties) :: Nil
 
     case SetNamespaceLocation(ResolvedNamespace(catalog, ns, _), location) =>
-      if (SparkStringUtils.isEmpty(location)) {
+      if (SparkStringUtils.isBlank(location)) {
         throw QueryExecutionErrors.invalidEmptyLocationError(location)
       }
       AlterNamespaceSetPropertiesExec(
@@ -651,7 +651,7 @@ class DataSourceV2Strategy(session: SparkSession) extends 
Strategy with Predicat
 
     case CreateNamespace(ResolvedNamespace(catalog, ns, _), ifNotExists, 
properties) =>
       val location = properties.get(SupportsNamespaces.PROP_LOCATION)
-      if (location.isDefined && location.get.isEmpty) {
+      if (location.exists(SparkStringUtils.isBlank)) {
         throw QueryExecutionErrors.invalidEmptyLocationError(location.get)
       }
       val finalProperties = 
properties.get(SupportsNamespaces.PROP_LOCATION).map { loc =>
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/AlterNamespaceSetLocationSuiteBase.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/AlterNamespaceSetLocationSuiteBase.scala
index 00484c2efc83..6fde7ea1fc86 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/AlterNamespaceSetLocationSuiteBase.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/AlterNamespaceSetLocationSuiteBase.scala
@@ -46,13 +46,15 @@ trait AlterNamespaceSetLocationSuiteBase extends QueryTest 
with DDLCommandTestUt
     val ns = s"$catalog.$namespace"
     withNamespace(ns) {
       sql(s"CREATE NAMESPACE $ns")
-      val sqlText = s"ALTER NAMESPACE $ns SET LOCATION ''"
-      checkError(
-        exception = intercept[SparkIllegalArgumentException] {
-          sql(sqlText)
-        },
-        condition = "INVALID_EMPTY_LOCATION",
-        parameters = Map("location" -> ""))
+      Seq("", " ", "\t", "\n", "\t\n", " \t ").foreach { location =>
+        val sqlText = s"ALTER NAMESPACE $ns SET LOCATION '$location'"
+        checkError(
+          exception = intercept[SparkIllegalArgumentException] {
+            sql(sqlText)
+          },
+          condition = "INVALID_EMPTY_LOCATION",
+          parameters = Map("location" -> location))
+      }
     }
   }
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/CreateNamespaceSuiteBase.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/CreateNamespaceSuiteBase.scala
index 9733b104beec..e3558d4bdaa5 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/CreateNamespaceSuiteBase.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/CreateNamespaceSuiteBase.scala
@@ -69,13 +69,15 @@ trait CreateNamespaceSuiteBase extends QueryTest with 
DDLCommandTestUtils {
         // The generated temp path is not qualified.
         val path = tmpDir.getCanonicalPath
         assert(!path.startsWith("file:/"))
-        val sqlText = s"CREATE NAMESPACE $ns LOCATION ''"
-        checkError(
-          exception = intercept[SparkIllegalArgumentException] {
-            sql(sqlText)
-          },
-          condition = "INVALID_EMPTY_LOCATION",
-          parameters = Map("location" -> ""))
+        Seq("", " ", "\t", "\n", "\t\n", " \t ").foreach { location =>
+          val sqlText = s"CREATE NAMESPACE $ns LOCATION '$location'"
+          checkError(
+            exception = intercept[SparkIllegalArgumentException] {
+              sql(sqlText)
+            },
+            condition = "INVALID_EMPTY_LOCATION",
+            parameters = Map("location" -> location))
+        }
         val uri = new Path(path).toUri
         sql(s"CREATE NAMESPACE $ns LOCATION '$uri'")
         // Make sure the location is qualified.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to