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 6563e6ab74be [SPARK-57287][SQL] Escape backslash in LIKE pattern for 
STARTS_WITH/ENDS_WITH/CONTAINS pushdown
6563e6ab74be is described below

commit 6563e6ab74be5cbb47728fef4d22ca5330bb262a
Author: Shrirang Mhalgi <[email protected]>
AuthorDate: Mon Jun 8 17:14:45 2026 -0700

    [SPARK-57287][SQL] Escape backslash in LIKE pattern for 
STARTS_WITH/ENDS_WITH/CONTAINS pushdown
    
    ### What changes were proposed in this pull request?
    Escape the backslash character in 
`V2ExpressionSQLBuilder.escapeSpecialCharsForLikePattern` so that 
`STARTS_WITH`, `ENDS_WITH`, and `CONTAINS` predicates containing backslashes 
produce correct `LIKE` patterns when pushed down to V2 data sources.
    
    ### Why are the changes needed?
    The LIKE patterns generated for predicate pushdown declare `ESCAPE '\'`, 
making backslash the escape character. However, 
`escapeSpecialCharsForLikePattern` only escapes `_` and `%` - it does not 
escape the backslash itself. When a filter value contains a backslash (e.g., 
`startsWith("abc\")`), the pushed SQL becomes LIKE `'abc\%'` where `\%` is 
interpreted as "literal %" rather than "literal backslash followed by wildcard 
%". This produces silently wrong results on the remote database.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes. V2 data source queries with `startsWith`, `endsWith`, or `contains` 
predicates on values containing backslashes now return correct results instead 
of silently wrong results.
    
    ### How was this patch tested?
    Added regression tests in `JDBCV2Suite` covering `startsWith`, `endsWith`, 
and `contains` with backslash values against the H2 test database. Tests fail 
without the fix and pass with it.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Yes. Authored using Claude Opus 4.6
    
    Closes #56350 from shrirangmhalgi/backslash-issue-fix.
    
    Authored-by: Shrirang Mhalgi <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit 3c31d68fc6aa1353f3f3e998246499e21e8092be)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../sql/connector/util/V2ExpressionSQLBuilder.java  |  1 +
 .../org/apache/spark/sql/jdbc/JDBCV2Suite.scala     | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git 
a/sql/catalyst/src/main/java/org/apache/spark/sql/connector/util/V2ExpressionSQLBuilder.java
 
b/sql/catalyst/src/main/java/org/apache/spark/sql/connector/util/V2ExpressionSQLBuilder.java
index 64727c0e40f2..8ff75973558c 100644
--- 
a/sql/catalyst/src/main/java/org/apache/spark/sql/connector/util/V2ExpressionSQLBuilder.java
+++ 
b/sql/catalyst/src/main/java/org/apache/spark/sql/connector/util/V2ExpressionSQLBuilder.java
@@ -67,6 +67,7 @@ public class V2ExpressionSQLBuilder {
       switch (c) {
         case '_' -> builder.append("\\_");
         case '%' -> builder.append("\\%");
+        case '\\' -> builder.append("\\\\");
         default -> builder.append(c);
       }
     }
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCV2Suite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCV2Suite.scala
index 95a0afa4d150..ba6945c7b1fd 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCV2Suite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCV2Suite.scala
@@ -214,6 +214,8 @@ class JDBCV2Suite extends SharedSparkSession with 
ExplainSuiteHelper {
       batchStmt.addBatch("INSERT INTO \"test\".\"address\" VALUES 
('abc%[email protected]')")
       batchStmt.addBatch("INSERT INTO \"test\".\"address\" VALUES 
('abc_%[email protected]')")
       batchStmt.addBatch("INSERT INTO \"test\".\"address\" VALUES 
('abc_''%[email protected]')")
+      batchStmt.addBatch(
+        "INSERT INTO \"test\".\"address\" VALUES ('abc\\[email protected]')")
 
       batchStmt.addBatch("CREATE TABLE \"test\".\"employee_bonus\" " +
         "(name TEXT(32), salary NUMERIC(20, 2), bonus DOUBLE, factor DOUBLE)")
@@ -1413,6 +1415,25 @@ class JDBCV2Suite extends SharedSparkSession with 
ExplainSuiteHelper {
     checkPushedInfo(df15,
       raw"PushedFilters: [EMAIL IS NOT NULL, EMAIL LIKE '%c\_''\%d%' ESCAPE 
'\']")
     checkAnswer(df15, Seq(Row("abc_'%[email protected]")))
+
+    // Backslash in the value must be escaped since '\' is the LIKE escape 
character
+    val df16 = 
spark.table("h2.test.address").filter($"email".startsWith("abc\\"))
+    checkFiltersRemoved(df16)
+    checkPushedInfo(df16,
+      raw"PushedFilters: [EMAIL IS NOT NULL, EMAIL LIKE 'abc\\%' ESCAPE '\']")
+    checkAnswer(df16, Seq(Row("abc\\[email protected]")))
+
+    val df17 = 
spark.table("h2.test.address").filter($"email".endsWith("\\[email protected]"))
+    checkFiltersRemoved(df17)
+    checkPushedInfo(df17,
+      raw"PushedFilters: [EMAIL IS NOT NULL, EMAIL LIKE '%\\[email protected]' 
ESCAPE '\']")
+    checkAnswer(df17, Seq(Row("abc\\[email protected]")))
+
+    val df18 = spark.table("h2.test.address").filter($"email".contains("c\\d"))
+    checkFiltersRemoved(df18)
+    checkPushedInfo(df18,
+      raw"PushedFilters: [EMAIL IS NOT NULL, EMAIL LIKE '%c\\d%' ESCAPE '\']")
+    checkAnswer(df18, Seq(Row("abc\\[email protected]")))
   }
 
   test("scan with filter push-down with ansi mode") {


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

Reply via email to