You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by we...@apache.org on 2026/05/31 01:03:11 UTC

(spark) branch master updated: [SPARK-53454][SQL] Handle AlwaysTrue/AlwaysFalse in JDBCSQLBuilder

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

cloud-fan 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 1062b5e0963e [SPARK-53454][SQL] Handle AlwaysTrue/AlwaysFalse in JDBCSQLBuilder
1062b5e0963e is described below

commit 1062b5e0963e2face450708cb5c50978779c3a1f
Author: Shrirang Mhalgi <sh...@gmail.com>
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 <sh...@gmail.com>
    Co-authored-by: Wenchen Fan <cl...@gmail.com>
    Signed-off-by: Wenchen Fan <we...@databricks.com>
---
 .../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: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org