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

maxgekk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 3d7ddfc83a5f [SPARK-49398][SQL] Improve the error for parameters in 
the query of CACHE TABLE and CREATE VIEW
3d7ddfc83a5f is described below

commit 3d7ddfc83a5f2a2da99f35ff4967f15c4511fc49
Author: Mikhail Nikoliukin <[email protected]>
AuthorDate: Wed Sep 11 10:52:55 2024 +0200

    [SPARK-49398][SQL] Improve the error for parameters in the query of CACHE 
TABLE and CREATE VIEW
    
    ### What changes were proposed in this pull request?
    
    Change type of error that is thrown on `CACHE TABLE` statement with 
parameter markers.
    (`UNBOUND_SQL_PARAMETER` -> 
`UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT`)
    
    ### Why are the changes needed?
    
    `UNBOUND_SQL_PARAMETER` is a confusing error for a user. From his point of 
view he can pass all parameters argument but still have the error.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, error type changed for `CACHE TABLE` statement with parameters
    
    ### How was this patch tested?
    
    Was added a new specific test, that would fail without fixes in this pr
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No
    
    Closes #48055 from mikhailnik-db/SPARK-49398.
    
    Authored-by: Mikhail Nikoliukin <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
---
 .../spark/sql/catalyst/parser/AstBuilder.scala     | 26 +++++++++++++++
 .../spark/sql/execution/SparkSqlParser.scala       | 24 +++-----------
 .../org/apache/spark/sql/ParametersSuite.scala     | 38 ++++++++++++++++++----
 3 files changed, 62 insertions(+), 26 deletions(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
index ab7936179917..205af9e33c17 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
@@ -5099,6 +5099,13 @@ class AstBuilder extends DataTypeAstBuilder
       val options = 
Option(ctx.options).map(visitPropertyKeyValues).getOrElse(Map.empty)
       val isLazy = ctx.LAZY != null
       if (query.isDefined) {
+        // Disallow parameter markers in the query of the cache.
+        // We need this limitation because we store the original query text, 
pre substitution.
+        // To lift this we would need to reconstitute the query with parameter 
markers replaced with
+        // the values given at CACHE TABLE time, or we would need to store the 
parameter values
+        // alongside the text.
+        // The same rule can be found in CREATE VIEW builder.
+        checkInvalidParameter(query.get, "the query of CACHE TABLE")
         CacheTableAsSelect(ident.head, query.get, source(ctx.query()), isLazy, 
options)
       } else {
         CacheTable(
@@ -5695,4 +5702,23 @@ class AstBuilder extends DataTypeAstBuilder
     withOrigin(ctx) {
       visitSetVariableImpl(ctx.query(), ctx.multipartIdentifierList(), 
ctx.assignmentList())
     }
+
+  /**
+   * Check plan for any parameters.
+   * If it finds any throws 
UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT.
+   * This method is used to ban parameters in some contexts.
+   */
+  protected def checkInvalidParameter(plan: LogicalPlan, statement: String): 
Unit = {
+    plan.foreach { p =>
+      p.expressions.foreach { expr =>
+        if (expr.containsPattern(PARAMETER)) {
+          throw QueryParsingErrors.parameterMarkerNotAllowed(statement, 
p.origin)
+        }
+      }
+    }
+    plan.children.foreach(p => checkInvalidParameter(p, statement))
+    plan.innerChildren.collect {
+      case child: LogicalPlan => checkInvalidParameter(child, statement)
+    }
+  }
 }
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
index 8f27a0e8f673..a8261e5d98ba 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
@@ -33,7 +33,6 @@ import org.apache.spark.sql.catalyst.expressions.{Expression, 
Literal}
 import org.apache.spark.sql.catalyst.parser._
 import org.apache.spark.sql.catalyst.parser.SqlBaseParser._
 import org.apache.spark.sql.catalyst.plans.logical._
-import org.apache.spark.sql.catalyst.trees.TreePattern.PARAMETER
 import org.apache.spark.sql.catalyst.util.DateTimeConstants
 import org.apache.spark.sql.errors.{QueryCompilationErrors, QueryParsingErrors}
 import org.apache.spark.sql.execution.command._
@@ -466,22 +465,6 @@ class SparkSqlAstBuilder extends AstBuilder {
     }
   }
 
-
-  private def checkInvalidParameter(plan: LogicalPlan, statement: String):
-  Unit = {
-    plan.foreach { p =>
-      p.expressions.foreach { expr =>
-        if (expr.containsPattern(PARAMETER)) {
-          throw QueryParsingErrors.parameterMarkerNotAllowed(statement, 
p.origin)
-        }
-      }
-    }
-    plan.children.foreach(p => checkInvalidParameter(p, statement))
-    plan.innerChildren.collect {
-      case child: LogicalPlan => checkInvalidParameter(child, statement)
-    }
-  }
-
   /**
    * Create or replace a view. This creates a [[CreateViewCommand]].
    *
@@ -537,12 +520,13 @@ class SparkSqlAstBuilder extends AstBuilder {
     }
     val qPlan: LogicalPlan = plan(ctx.query)
 
-    // Disallow parameter markers in the body of the view.
+    // Disallow parameter markers in the query of the view.
     // We need this limitation because we store the original query text, pre 
substitution.
-    // To lift this we would need to reconstitute the body with parameter 
markers replaced with the
+    // To lift this we would need to reconstitute the query with parameter 
markers replaced with the
     // values given at CREATE VIEW time, or we would need to store the 
parameter values alongside
     // the text.
-    checkInvalidParameter(qPlan, "CREATE VIEW body")
+    // The same rule can be found in CACHE TABLE builder.
+    checkInvalidParameter(qPlan, "the query of CREATE VIEW")
     if (viewType == PersistedView) {
       val originalText = source(ctx.query)
       assert(Option(originalText).isDefined,
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/ParametersSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/ParametersSuite.scala
index 511d1e87309a..c90b34d45e78 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/ParametersSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/ParametersSuite.scala
@@ -247,7 +247,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -262,7 +262,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -277,7 +277,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -292,7 +292,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -311,7 +311,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -330,7 +330,7 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
         spark.sql(sqlText, args)
       },
       condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
-      parameters = Map("statement" -> "CREATE VIEW body"),
+      parameters = Map("statement" -> "the query of CREATE VIEW"),
       context = ExpectedContext(
         fragment = sqlText,
         start = 0,
@@ -715,4 +715,30 @@ class ParametersSuite extends QueryTest with 
SharedSparkSession with PlanTest {
     spark.sessionState.analyzer.executeAndCheck(analyzedPlan, 
df.queryExecution.tracker)
     checkAnswer(df, Row(11))
   }
+
+  test("SPARK-49398: Cache Table with parameter markers in select query should 
throw " +
+    "UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT") {
+    val sqlText = "CACHE TABLE CacheTable as SELECT 1 + :param1"
+    checkError(
+      exception = intercept[AnalysisException] {
+        spark.sql(sqlText, Map("param1" -> "1")).show()
+      },
+      condition = 
"UNSUPPORTED_FEATURE.PARAMETER_MARKER_IN_UNEXPECTED_STATEMENT",
+      parameters = Map("statement" -> "the query of CACHE TABLE"),
+      context = ExpectedContext(
+        fragment = sqlText,
+        start = 0,
+        stop = sqlText.length - 1)
+    )
+  }
+
+  test("SPARK-49398: Cache Table with parameter in identifier should work") {
+    val cacheName = "MyCacheTable"
+    withCache(cacheName) {
+      spark.sql("CACHE TABLE IDENTIFIER(:param) as SELECT 1 as c1", 
Map("param" -> cacheName))
+      checkAnswer(
+        spark.sql("SHOW COLUMNS FROM IDENTIFIER(?)", args = Array(cacheName)),
+        Row("c1"))
+    }
+  }
 }


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

Reply via email to