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

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


The following commit(s) were added to refs/heads/branch-4.3 by this push:
     new f1f1bebe3e95 [SPARK-56558][SQL] CTAS IF NOT EXISTS bypasses non-empty 
location check when table does not exist in catalog
f1f1bebe3e95 is described below

commit f1f1bebe3e9514d2d4b82950d7bab7c6d3d46559
Author: Pablo Langa <[email protected]>
AuthorDate: Fri Aug 7 20:49:42 2026 +0800

    [SPARK-56558][SQL] CTAS IF NOT EXISTS bypasses non-empty location check 
when table does not exist in catalog
    
    ### What changes were proposed in this pull request?
    
    CREATE TABLE IF NOT EXISTS ... LOCATION ... AS SELECT silently overwrites 
data in a non-empty directory when the table does not yet exist in the catalog, 
bypassing the protection introduced in 
https://github.com/apache/spark/pull/32411.
    IF NOT EXISTS semantics apply only to the catalog: if the table already 
exists, the command is a no-op. If the table does not exist in the catalog, the 
non-empty location check must still be enforced, identical to a plain CREATE 
TABLE ... AS SELECT.
    
    This PR aligns the behaviour across the two scenarios CREATE TABLE and 
CREATE TABLE IF NOT EXISTS when the table do not exists.
    
    ### Why are the changes needed?
    
    Aling the behavior between CREATE TABLE and CREATE TABLE IF NOT EXISTS
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, now CREATE TABLE IF NOT EXISTS will fail if the location is not empty 
like CREATE TABLE
    
    ### How was this patch tested?
    
    Tests and manually
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    yes, co-authored by Claude Code
    
    Closes #55444 from planga82/fix/create_table_existing_location.
    
    Authored-by: Pablo Langa <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit 44551f920250dda0ce9b50374bbd912c4c28a029)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../sql/execution/command/DataWritingCommand.scala |  4 ++--
 .../execution/datasources/TableLocationSuite.scala | 23 +++++++++++++++++++++-
 .../spark/sql/hive/execution/SQLQuerySuite.scala   | 23 ++++++++++++++++++++++
 3 files changed, 47 insertions(+), 3 deletions(-)

diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/DataWritingCommand.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/DataWritingCommand.scala
index 90050b25e954..e5ff8cd5a564 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/DataWritingCommand.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/DataWritingCommand.scala
@@ -103,14 +103,14 @@ object DataWritingCommand {
   }
   /**
    * When execute CTAS operators, and the location is not empty, throw 
[[AnalysisException]].
-   * For CTAS, the SaveMode is always [[ErrorIfExists]]
    *
    * @param tablePath Table location.
    * @param saveMode  Save mode of the table.
    * @param hadoopConf Configuration.
    */
   def assertEmptyRootPath(tablePath: URI, saveMode: SaveMode, hadoopConf: 
Configuration): Unit = {
-    if (saveMode == SaveMode.ErrorIfExists && 
!SQLConf.get.allowNonEmptyLocationInCTAS) {
+    if ((saveMode == SaveMode.ErrorIfExists || saveMode == SaveMode.Ignore) &&
+        !SQLConf.get.allowNonEmptyLocationInCTAS) {
       val filePath = new org.apache.hadoop.fs.Path(tablePath)
       val fs = filePath.getFileSystem(hadoopConf)
       if (fs.exists(filePath) &&
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/TableLocationSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/TableLocationSuite.scala
index dacb745f49ae..5aa165d22787 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/TableLocationSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/TableLocationSuite.scala
@@ -19,7 +19,7 @@ package org.apache.spark.sql.execution.datasources
 
 import org.apache.hadoop.fs.Path
 
-import org.apache.spark.sql.AnalysisException
+import org.apache.spark.sql.{AnalysisException, Row}
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSession
 
@@ -76,4 +76,25 @@ class TableLocationSuite extends SharedSparkSession {
       }
     }
   }
+
+  test("SPARK-56558: CTAS IF NOT EXISTS should be with non-existent or empty 
location") {
+    withSQLConf(SQLConf.ALLOW_NON_EMPTY_LOCATION_IN_CTAS.key -> "false") {
+      withTempDir { dir =>
+        val tempLocation = dir.getCanonicalPath
+        withTable("ctas1", "ctas2") {
+          sql(s"CREATE TABLE ctas1 USING parquet LOCATION 
'$tempLocation/ctas1' " +
+            "AS SELECT 1 AS ID")
+          // Table ctas2 does not exist in the catalog, so IF NOT EXISTS must 
not skip the
+          // non-empty location check and overwrite the data of table ctas1.
+          val m = intercept[AnalysisException] {
+            sql(s"CREATE TABLE IF NOT EXISTS ctas2 USING parquet LOCATION 
'$tempLocation/ctas1' " +
+              "AS SELECT 2 AS ID")
+          }.getMessage
+          assert(m.contains("CREATE-TABLE-AS-SELECT cannot create table with 
location to a " +
+            "non-empty directory"))
+          checkAnswer(spark.table("ctas1"), Row(1))
+        }
+      }
+    }
+  }
 }
diff --git 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/SQLQuerySuite.scala
 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/SQLQuerySuite.scala
index 68f46cbcb112..74d317ba1e58 100644
--- 
a/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/SQLQuerySuite.scala
+++ 
b/sql/hive/src/test/scala/org/apache/spark/sql/hive/execution/SQLQuerySuite.scala
@@ -594,6 +594,29 @@ abstract class SQLQuerySuiteBase extends QueryTest with 
TestHiveSingleton {
     }
   }
 
+  test("SPARK-56558: CTAS IF NOT EXISTS Hive Table should be with non-existent 
" +
+    "or empty location") {
+    withSQLConf(SQLConf.ALLOW_NON_EMPTY_LOCATION_IN_CTAS.key -> "false") {
+      withTempDir { dir =>
+        val tempLocation = dir.toURI.toString
+        withTable("ctas1", "ctas_with_existing_location") {
+          sql(s"CREATE TABLE ctas1(id string) stored as rcfile LOCATION 
'$tempLocation/ctas1'")
+          sql("INSERT INTO TABLE ctas1 SELECT 'A' ")
+          // The target table does not exist in the catalog, so IF NOT EXISTS 
must not skip the
+          // non-empty location check and overwrite the data of table ctas1.
+          val m = intercept[AnalysisException] {
+            sql(s"""CREATE TABLE IF NOT EXISTS ctas_with_existing_location 
stored as rcfile
+                 |LOCATION '$tempLocation'
+                 |AS SELECT key k, value FROM src ORDER BY k, 
value""".stripMargin)
+          }.getMessage
+          assert(m.contains("CREATE-TABLE-AS-SELECT cannot create " +
+            "table with location to a non-empty directory"))
+          checkAnswer(spark.table("ctas1"), Row("A"))
+        }
+      }
+    }
+  }
+
   test("CTAS with serde") {
     withTable("ctas1", "ctas2", "ctas3", "ctas4", "ctas5") {
       sql("CREATE TABLE ctas1 AS SELECT key k, value FROM src ORDER BY k, 
value")


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

Reply via email to