[
https://issues.apache.org/jira/browse/SPARK-59672?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
ASF GitHub Bot updated SPARK-59672:
-----------------------------------
Labels: pull-request-available (was: )
> SQL parameter binding FAILS with UNBOUND_SQL_PARAMETER when a session
> extension injects a parser (4.1 regression)
> -----------------------------------------------------------------------------------------------------------------
>
> Key: SPARK-59672
> URL: https://issues.apache.org/jira/browse/SPARK-59672
> Project: Spark
> Issue Type: Bug
> Components: SQL
> Affects Versions: 4.1.0, 4.1.1, 4.1.2, 4.1.3
> Reporter: Francisco A. B. Sampaio
> Priority: Major
> Labels: pull-request-available
>
> Since 4.1.0, {{spark.sql(query, args)}} fails to bind parameters whenever a
> session extension is configured via {{spark.sql.extensions}}. Both positional
> ({{?}}) and named ({{:name}}) parameters fail with {{UNBOUND_SQL_PARAMETER}}.
> Unsetting {{spark.sql.extensions}} on the same build makes them work. 4.0.x
> is unaffected.
> Reproduced with two unrelated extensions, Delta Lake 4.1.0
> ({{io.delta.sql.DeltaSparkSessionExtension}}) and Apache Iceberg 1.11.0, so
> it is not specific to either project. Both inject a parser through
> {{SparkSessionExtensions.injectParser}}.
> h3. Root cause
> SPARK-53573 moved parameter handling into a parser pre-processor, and
> {{SparkSession.sql}} now calls {{ParserInterface.parsePlanWithParameters}}.
> That method has a default implementation that discards the parameters:
> {code:scala}
> def parsePlanWithParameters(sqlText: String, parameterContext:
> ParameterContext): LogicalPlan = {
> // Default implementation falls back to regular parsePlan
> // Concrete implementations can override this for parameter support
> parsePlan(sqlText)
> }
> {code}
> Only {{SparkSqlParser}} overrides it. A parser injected by an extension
> inherits the default, so the values never reach the parser and the markers
> arrive at the analyzer unbound.
> h3. Reproduction (spark-shell, no Spark Connect involved)
> {code:scala}
> // spark-shell --conf
> spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension
> spark.sql("SELECT ? AS id", ArrayAny).collect()
> // org.apache.spark.sql.AnalysisException: [UNBOUND_SQL_PARAMETER] Found the
> unbound parameter: _7.
> spark.sql("SELECT :id AS id", Map("id" -> 42)).collect()
> // [UNBOUND_SQL_PARAMETER] Found the unbound parameter: id.
> // same build, extension unset: both return [42]
> {code}
> h3. Results
> ||Spark||spark.sql.extensions||Parameter binding||
> |4.1.0 - 4.1.3|Delta 4.1.0|fails|
> |4.1.3|Iceberg 1.11.0|fails|
> |4.1.3|unset|works|
> |4.0.4|Delta 4.0.1|works|
> |4.0.4|Iceberg 1.10.2|works|
> Setting only {{spark.sql.catalog.spark_catalog}} (Delta's catalog, without
> the extension) does not trigger it.
> Also reproducible through Spark Connect, for both the {{SQL}} relation and
> the {{SqlCommand}} command, using {{pos_arguments}}, {{named_arguments}} and
> the deprecated {{pos_args}}.
> h3. Workaround
> Setting {{spark.sql.legacy.parameterSubstitution.constantsOnly=true}}
> restores binding for classic {{spark.sql(query, args)}} and for the Connect
> {{SqlCommand}} path, because the analyzer binds the markers instead. It does
> not help the Connect {{SQL}} relation path, which never applies the legacy
> fallback - that looks like a separate defect and I can file it separately.
> h3. Impact
> Parameterized SQL is the standard defence against SQL injection. On 4.1.x it
> fails outright for anyone using Delta, Iceberg or any other session
> extension, pushing users toward string interpolation.
> A PR is ready: the default implementation binds the parameters during
> analysis so parsers that only override {{parsePlan}} keep working.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]