This is an automated email from the ASF dual-hosted git repository.
cloud-fan 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 2769578f64f5 [SPARK-53454][SQL] Handle AlwaysTrue/AlwaysFalse in
JDBCSQLBuilder
2769578f64f5 is described below
commit 2769578f64f5c04349579e5be50f3fb04478be26
Author: Shrirang Mhalgi <[email protected]>
AuthorDate: Sun May 31 09:02:50 2026 +0800
[SPARK-53454][SQL] Handle AlwaysTrue/AlwaysFalse in JDBCSQLBuilder
### What changes were proposed in this pull request?
Handle `AlwaysTrue` and `AlwaysFalse` predicates in
`JDBCSQLBuilder.build()` by producing `"1 = 1"` and `"1 = 0"` respectively.
`AlwaysTrue`/`AlwaysFalse` implement `Literal<Boolean>`, so they previously
matched the `Literal` branch and produced bare `"TRUE"`/`"FALSE"` strings via
`visitLiteral()`. Some databases (Oracle, DB2) do not support these as boolean
expressions in WHERE clauses.
The fix is placed in `JDBCSQLBuilder` (not the base V2ExpressionSQLBuilder)
so that only the JDBC wire path gets the portable form, while
`ToStringSQLBuilder` continues emitting `TRUE/FALSE` for human-readable display
(EXPLAIN, PushedFilters info).
### Why are the changes needed?
When AQE simplifies predicates to `AlwaysTrue`/`AlwaysFalse` and they are
pushed down to JDBC, the generated SQL contains bare `TRUE`/`FALSE` which fails
on databases that don't support boolean literals.
This is a fresh, simplified approach per reviewer feedback on the original
PR #52198 (auto-closed due to inactivity) - using `1=1`/`1=0` universally
without a dialect-specific API.
### Does this PR introduce _any_ user-facing change?
Yes. JDBC pushed filter SQL now uses `1 = 1`/`1 = 0` instead of
`TRUE`/`FALSE` for `AlwaysTrue/AlwaysFalse` predicates. This fixes queries that
previously failed on Oracle/DB2.
### How was this patch tested?
- Added unit test in `JDBCSuite` (using NoopDialect to test
`JDBCSQLBuilder` directly)
- Full JDBC test suite passes
### Was this patch authored or co-authored using generative AI tooling?
Yes.
Closes #56085 from shrirangmhalgi/SPARK-53454-jdbc-always-true-false.
Lead-authored-by: Shrirang Mhalgi <[email protected]>
Co-authored-by: Wenchen Fan <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit 1062b5e0963e2face450708cb5c50978779c3a1f)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../main/scala/org/apache/spark/sql/jdbc/JdbcDialects.scala | 10 +++++++++-
.../src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala | 7 +++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git
a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/JdbcDialects.scala
b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/JdbcDialects.scala
index 2c915c734b24..3b5182721c29 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/JdbcDialects.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/JdbcDialects.scala
@@ -41,7 +41,7 @@ import
org.apache.spark.sql.connector.catalog.functions.UnboundFunction
import org.apache.spark.sql.connector.catalog.index.TableIndex
import org.apache.spark.sql.connector.expressions.{Expression, Literal,
NamedReference}
import org.apache.spark.sql.connector.expressions.aggregate.AggregateFunc
-import org.apache.spark.sql.connector.expressions.filter.Predicate
+import org.apache.spark.sql.connector.expressions.filter.{AlwaysFalse,
AlwaysTrue, Predicate}
import org.apache.spark.sql.connector.util.V2ExpressionSQLBuilder
import org.apache.spark.sql.errors.QueryCompilationErrors
import org.apache.spark.sql.execution.datasources.jdbc.{DriverRegistry,
JDBCOptions, JdbcOptionsInWrite, JdbcUtils}
@@ -400,6 +400,14 @@ abstract class JdbcDialect extends Serializable with
Logging {
}
private[jdbc] class JDBCSQLBuilder extends V2ExpressionSQLBuilder {
+ // SPARK-53454: Produce portable SQL for AlwaysTrue/AlwaysFalse predicates.
+ // Some databases (Oracle, DB2) do not support bare TRUE/FALSE in WHERE
clauses.
+ override def build(expr: Expression): String = expr match {
+ case _: AlwaysTrue => "1 = 1"
+ case _: AlwaysFalse => "1 = 0"
+ case _ => super.build(expr)
+ }
+
// Some dialects do not support boolean type and this convenient util
function is
// provided to generate SQL string without boolean values.
protected def inputToSQLNoBool(input: Expression): String = input match {
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
index f79e63921627..e3263c3cf357 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
@@ -35,6 +35,7 @@ import org.apache.spark.sql.catalyst.{analysis,
TableIdentifier}
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
import org.apache.spark.sql.catalyst.plans.logical.ShowCreateTable
import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap,
CharVarcharUtils, DateTimeTestUtils}
+import org.apache.spark.sql.connector.expressions.filter.{AlwaysFalse,
AlwaysTrue}
import org.apache.spark.sql.execution.{DataSourceScanExec, ExtendedMode,
ProjectExec}
import org.apache.spark.sql.execution.command.{ExplainCommand,
ShowCreateTableCommand}
import org.apache.spark.sql.execution.datasources.{LogicalRelation,
LogicalRelationWithTable}
@@ -892,6 +893,12 @@ class JDBCSuite extends SharedSparkSession {
assert(doCompileFilter(EqualTo("col0.nested", 3)).isEmpty)
}
+ test("SPARK-53454: AlwaysTrue/AlwaysFalse compile to portable SQL in
JDBCSQLBuilder") {
+ val dialect = JdbcDialects.get("jdbc:")
+ assert(dialect.compileExpression(new AlwaysTrue).get === "1 = 1")
+ assert(dialect.compileExpression(new AlwaysFalse).get === "1 = 0")
+ }
+
test("Dialect unregister") {
JdbcDialects.unregisterDialect(H2Dialect())
try {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]