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]