From f2fb46aac5731f7492b40efac1e7f9ebdac45b26 Mon Sep 17 00:00:00 2001 From: Daniel Lunin Date: Wed, 1 Jul 2026 20:18:04 +0000 Subject: [PATCH] [SPARK-57756][SQL][TESTS] Add buildSQLFunctionConf ANSI-invariant tests Add unit tests for Analyzer.buildSQLFunctionConf covering ANSI handling: a persisted ANSI value makes the alwaysSetAnsiValue flag a no-op, and the flag only takes effect when ANSI is not persisted (under ASSUME_ANSI_FALSE_IF_NOT_PERSISTED). --- .../sql/catalyst/analysis/AnalysisSuite.scala | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnalysisSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnalysisSuite.scala index 4a7adcc050a00..48da5ddd8cd3b 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnalysisSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnalysisSuite.scala @@ -29,8 +29,8 @@ import org.scalatest.matchers.must.Matchers import org.apache.spark.SparkException import org.apache.spark.api.python.PythonEvalType import org.apache.spark.sql.AnalysisException -import org.apache.spark.sql.catalyst.{AliasIdentifier, QueryPlanningTracker, TableIdentifier} -import org.apache.spark.sql.catalyst.catalog.{InMemoryCatalog, SessionCatalog} +import org.apache.spark.sql.catalyst.{AliasIdentifier, FunctionIdentifier, QueryPlanningTracker, TableIdentifier} +import org.apache.spark.sql.catalyst.catalog.{InMemoryCatalog, SessionCatalog, SQLFunction} import org.apache.spark.sql.catalyst.dsl.expressions._ import org.apache.spark.sql.catalyst.dsl.plans._ import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder @@ -1894,6 +1894,69 @@ class AnalysisSuite extends AnalysisTest with Matchers { val expectedPlan = Project(Seq(UnresolvedAttribute("i")), addColumnF).analyze checkAnalysis(inputPlan, expectedPlan) } + + private def scalarSQLFunctionWithProps( + props: Map[String, String]): SQLFunction = { + val intType: DataType = IntegerType + val scalarReturnType: Either[DataType, StructType] = scala.Left(intType) + SQLFunction( + name = FunctionIdentifier("ansi_probe"), + inputParam = Some(new StructType().add("x", intType)), + returnType = scalarReturnType, + exprText = Some("x + 1"), + queryText = None, + comment = None, + collation = None, + deterministic = Some(true), + containsSQL = Some(true), + isTableFunc = false, + properties = props) + } + + gridTest("SQL UDF body-resolution ANSI: persisted ANSI makes alwaysSetAnsiValue a no-op")( + Seq(false, true)) { applySessionOverrides => + val ansiKey = SQLConf.ANSI_ENABLED.key + val fnWithPersistedAnsi = scalarSQLFunctionWithProps(Map(s"sqlConfig.$ansiKey" -> "false")) + + withSQLConf( + SQLConf.ASSUME_ANSI_FALSE_IF_NOT_PERSISTED.key -> "true", + SQLConf.APPLY_SESSION_CONF_OVERRIDES_TO_FUNCTION_RESOLUTION.key -> + applySessionOverrides.toString, + ansiKey -> "true") { + val withFlag = Analyzer.buildSQLFunctionConf( + function = fnWithPersistedAnsi, + applySessionOverrides = applySessionOverrides, + alwaysSetAnsiValue = true) + val withoutFlag = Analyzer.buildSQLFunctionConf( + function = fnWithPersistedAnsi, + applySessionOverrides = applySessionOverrides, + alwaysSetAnsiValue = false) + assert(withFlag.settings.get(ansiKey) == "false") + assert(withoutFlag.settings.get(ansiKey) == "false") + assert(withFlag.settings.get(ansiKey) == withoutFlag.settings.get(ansiKey)) + } + } + + test("SQL UDF body-resolution ANSI: flag only bites when ANSI is unpersisted") { + val ansiKey = SQLConf.ANSI_ENABLED.key + val fnWithoutPersistedAnsi = scalarSQLFunctionWithProps(Map.empty) + + withSQLConf( + SQLConf.ASSUME_ANSI_FALSE_IF_NOT_PERSISTED.key -> "true", + SQLConf.APPLY_SESSION_CONF_OVERRIDES_TO_FUNCTION_RESOLUTION.key -> "false", + ansiKey -> "true") { + val withFlag = Analyzer.buildSQLFunctionConf( + function = fnWithoutPersistedAnsi, + applySessionOverrides = false, + alwaysSetAnsiValue = true) + val withoutFlag = Analyzer.buildSQLFunctionConf( + function = fnWithoutPersistedAnsi, + applySessionOverrides = false, + alwaysSetAnsiValue = false) + assert(withFlag.settings.get(ansiKey) == "false") + assert(withoutFlag.settings.get(ansiKey) == null) + } + } } /**