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

szehon-ho 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 90fe4487876d [SPARK-57360][SQL] Block temporary variables in generated 
column expressions
90fe4487876d is described below

commit 90fe4487876d927b43713f27430f59905eaca0bb
Author: Szehon Ho <[email protected]>
AuthorDate: Thu Jun 11 16:50:58 2026 -0700

    [SPARK-57360][SQL] Block temporary variables in generated column expressions
    
    ### What changes were proposed in this pull request?
    This PR adds a validation that rejects references to session (temporary) 
variables in a generated column's generation expression, for both `CREATE 
TABLE` and `REPLACE TABLE`. The check is added to the central 
`GeneratedColumnExpression.validate` and throws 
`UNSUPPORTED_EXPRESSION_GENERATED_COLUMN` with a clear reason.
    
    ### Why are the changes needed?
    This is a regression from #54126 ([SPARK-55347][SQL] Pass Generated Column 
as Expression to DSV2). Previously, the generation expression was analyzed by 
an independent (standalone) analyzer that did not resolve session variables, so 
a generated column referencing a temporary variable was not accepted. After 
#54126 moved the analysis inline into the main analyzer (similar to how 
constraint expressions are analyzed), the analyzer's last-resort variable 
resolution now resolves the refere [...]
    
    Persisting a session-scoped, mutable value into a generation expression is 
ill-defined: the value recomputed when reading or rewriting the column can 
differ across sessions or over time. The SQL standard restricts generation 
expressions to be deterministic and to reference only other columns of the same 
row, and other engines explicitly disallow variables:
    - **MySQL**: "Variables (system variables, user-defined variables, and 
stored program local variables) are not permitted."
    - **HSQLDB** (close to the standard): the expression "must reference only 
other, non-generated, columns of the table in the same row ... must be 
deterministic and must not access SQL-data."
    
    This restores the previous protection and aligns Spark with the standard.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes. A `CREATE TABLE` / `REPLACE TABLE` whose generated column references a 
temporary variable now fails analysis with 
`UNSUPPORTED_EXPRESSION_GENERATED_COLUMN` (reason: "generation expression 
cannot reference temporary variables") instead of being accepted.
    
    Example:
    ```sql
    DECLARE my_var INT DEFAULT 1;
    CREATE TABLE t(a INT, b INT GENERATED ALWAYS AS (a + my_var)) USING foo;
    -- now fails with UNSUPPORTED_EXPRESSION_GENERATED_COLUMN
    ```
    
    ### How was this patch tested?
    Added unit tests in `DataSourceV2SQLSuite` covering both `CREATE TABLE` and 
`REPLACE TABLE`.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Generated-by: Cursor (Claude Opus 4.8)
    
    Closes #56421 from szehon-ho/block_temp_variable_from_generated_column.
    
    Authored-by: Szehon Ho <[email protected]>
    Signed-off-by: Szehon Ho <[email protected]>
    (cherry picked from commit 334952601082732e809ec12cf11319a60dd32ea4)
    Signed-off-by: Szehon Ho <[email protected]>
---
 .../plans/logical/GeneratedColumnExpression.scala  | 10 ++++++++-
 .../spark/sql/connector/DataSourceV2SQLSuite.scala | 24 ++++++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/GeneratedColumnExpression.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/GeneratedColumnExpression.scala
index 28548dacdfbd..6253ccccafc3 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/GeneratedColumnExpression.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/GeneratedColumnExpression.scala
@@ -19,7 +19,7 @@ package org.apache.spark.sql.catalyst.plans.logical
 
 import org.apache.spark.sql.AnalysisException
 import org.apache.spark.sql.catalyst.expressions.{AnalysisAwareExpression, 
AttributeReference, Cast, Expression, UnaryExpression, Unevaluable}
-import 
org.apache.spark.sql.catalyst.trees.TreePattern.{ANALYSIS_AWARE_EXPRESSION, 
PLAN_EXPRESSION, TreePattern}
+import 
org.apache.spark.sql.catalyst.trees.TreePattern.{ANALYSIS_AWARE_EXPRESSION, 
PLAN_EXPRESSION, TreePattern, VARIABLE_REFERENCE}
 import org.apache.spark.sql.catalyst.util.V2ExpressionBuilder
 import org.apache.spark.sql.connector.catalog.GenerationExpression
 import org.apache.spark.sql.internal.SQLConf
@@ -57,6 +57,7 @@ case class GeneratedColumnExpression(
    * - The expression must be deterministic
    * - The expression data type can be safely up-cast to the destination 
column data type
    * - No subquery expressions
+   * - No references to session (temporary) variables
    * - No non-UTF8 binary collation
    */
   def validate(
@@ -77,6 +78,13 @@ case class GeneratedColumnExpression(
       throw unsupportedExpressionError("subquery expressions are not allowed 
for generated columns")
     }
 
+    // Don't allow references to session (temporary) variables. They are 
session-scoped mutable
+    // state, so persisting them into a generation expression would be 
ill-defined.
+    if (child.containsPattern(VARIABLE_REFERENCE)) {
+      throw unsupportedExpressionError(
+        "generation expression cannot reference temporary variables")
+    }
+
     // Use the resolver to respect case sensitivity settings
     val resolver = SQLConf.get.resolver
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala
index a5480d579a59..752c8ed6c22e 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2SQLSuite.scala
@@ -2020,6 +2020,30 @@ class DataSourceV2SQLSuiteV1Filter
     }
   }
 
+  test("SPARK-57360: generation expression cannot reference temporary 
variables") {
+    val tblName = "my_tab"
+    withSessionVariable("my_var") {
+      sql("DECLARE OR REPLACE VARIABLE my_var INT DEFAULT 1")
+      checkUnsupportedGenerationExpressionForCreate(
+        tblName,
+        "a + my_var",
+        "generation expression cannot reference temporary variables"
+      )
+    }
+  }
+
+  test("SPARK-57360: REPLACE TABLE - generation expression cannot reference 
temporary variables") {
+    val tblName = "my_tab"
+    withSessionVariable("my_var") {
+      sql("DECLARE OR REPLACE VARIABLE my_var INT DEFAULT 1")
+      checkUnsupportedGenerationExpressionForReplace(
+        tblName,
+        "a + my_var",
+        "generation expression cannot reference temporary variables"
+      )
+    }
+  }
+
   test("SPARK-44313: generation expression validation passes when there is a 
char/varchar column") {
     val tblName = "my_tab"
     // InMemoryTableCatalog.capabilities() = 
{SUPPORTS_CREATE_TABLE_WITH_GENERATED_COLUMNS}


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

Reply via email to