fix: correctly escape backslash in queries (#10522)

# Which Problems Are Solved

While investigating a support ticket, it was discovered that some
queries using equals or not equals without case matching were not
correctly escaping the value to compare. If a value contained a
backslash (`\`) the row would not match.

# How the Problems Are Solved

- Fixed the escaping for backslash for `like` operations.
- Changed equals and not equals comparison without case matching to `=`
instead of `like`.

# Additional Changes

None

# Additional Context

- related to a support request
- requires backport to v.3 and v4.x
This commit is contained in:
Livio Spring
2025-08-21 07:25:23 +02:00
committed by GitHub
parent a28950661c
commit 6c8d027e72
3 changed files with 26 additions and 14 deletions

View File

@@ -206,6 +206,7 @@ func (c Config) Type() dialect.DatabaseType {
} }
func EscapeLikeWildcards(value string) string { func EscapeLikeWildcards(value string) string {
value = strings.ReplaceAll(value, "\\", "\\\\")
value = strings.ReplaceAll(value, "%", "\\%") value = strings.ReplaceAll(value, "%", "\\%")
value = strings.ReplaceAll(value, "_", "\\_") value = strings.ReplaceAll(value, "_", "\\_")
return value return value

View File

@@ -288,9 +288,7 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*textQuery,
} }
// handle the comparisons which use (i)like and therefore need to escape potential wildcards in the value // handle the comparisons which use (i)like and therefore need to escape potential wildcards in the value
switch compare { switch compare {
case TextEqualsIgnoreCase, case TextStartsWith,
TextNotEqualsIgnoreCase,
TextStartsWith,
TextStartsWithIgnoreCase, TextStartsWithIgnoreCase,
TextEndsWith, TextEndsWith,
TextEndsWithIgnoreCase, TextEndsWithIgnoreCase,
@@ -300,6 +298,8 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*textQuery,
case TextEquals, case TextEquals,
TextListContains, TextListContains,
TextNotEquals, TextNotEquals,
TextEqualsIgnoreCase,
TextNotEqualsIgnoreCase,
textCompareMax: textCompareMax:
// do nothing // do nothing
} }
@@ -335,9 +335,9 @@ func (q *textQuery) comp() sq.Sqlizer {
case TextNotEquals: case TextNotEquals:
return sq.NotEq{q.Column.identifier(): q.Text} return sq.NotEq{q.Column.identifier(): q.Text}
case TextEqualsIgnoreCase: case TextEqualsIgnoreCase:
return sq.Like{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)} return sq.Eq{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)}
case TextNotEqualsIgnoreCase: case TextNotEqualsIgnoreCase:
return sq.NotLike{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)} return sq.NotEq{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)}
case TextStartsWith: case TextStartsWith:
return sq.Like{q.Column.identifier(): q.Text + "%"} return sq.Like{q.Column.identifier(): q.Text + "%"}
case TextStartsWithIgnoreCase: case TextStartsWithIgnoreCase:

View File

@@ -862,7 +862,7 @@ func TestNewTextQuery(t *testing.T) {
}, },
want: &textQuery{ want: &textQuery{
Column: testCol, Column: testCol,
Text: "hu\\%rst", Text: "hu%rst",
Compare: TextEqualsIgnoreCase, Compare: TextEqualsIgnoreCase,
}, },
}, },
@@ -875,7 +875,7 @@ func TestNewTextQuery(t *testing.T) {
}, },
want: &textQuery{ want: &textQuery{
Column: testCol, Column: testCol,
Text: "hu\\_rst", Text: "hu_rst",
Compare: TextEqualsIgnoreCase, Compare: TextEqualsIgnoreCase,
}, },
}, },
@@ -888,7 +888,7 @@ func TestNewTextQuery(t *testing.T) {
}, },
want: &textQuery{ want: &textQuery{
Column: testCol, Column: testCol,
Text: "h\\_urst\\%", Text: "h_urst%",
Compare: TextEqualsIgnoreCase, Compare: TextEqualsIgnoreCase,
}, },
}, },
@@ -914,7 +914,7 @@ func TestNewTextQuery(t *testing.T) {
}, },
want: &textQuery{ want: &textQuery{
Column: testCol, Column: testCol,
Text: "h\\_urst\\%", Text: "h_urst%",
Compare: TextNotEqualsIgnoreCase, Compare: TextNotEqualsIgnoreCase,
}, },
}, },
@@ -1204,7 +1204,7 @@ func TestTextQuery_comp(t *testing.T) {
Compare: TextEqualsIgnoreCase, Compare: TextEqualsIgnoreCase,
}, },
want: want{ want: want{
query: sq.Like{"LOWER(test_table.test_col)": "hurst"}, query: sq.Eq{"LOWER(test_table.test_col)": "hurst"},
}, },
}, },
{ {
@@ -1226,7 +1226,7 @@ func TestTextQuery_comp(t *testing.T) {
Compare: TextNotEqualsIgnoreCase, Compare: TextNotEqualsIgnoreCase,
}, },
want: want{ want: want{
query: sq.NotLike{"LOWER(test_table.test_col)": "hurst"}, query: sq.NotEq{"LOWER(test_table.test_col)": "hurst"},
}, },
}, },
{ {
@@ -1237,7 +1237,18 @@ func TestTextQuery_comp(t *testing.T) {
Compare: TextEqualsIgnoreCase, Compare: TextEqualsIgnoreCase,
}, },
want: want{ want: want{
query: sq.Like{"LOWER(test_table.test_col)": "hu\\%\\%rst"}, query: sq.Eq{"LOWER(test_table.test_col)": "hu%%rst"},
},
},
{
name: "equals ignore case backslash",
fields: fields{
Column: testCol,
Text: "AD\\Hurst",
Compare: TextEqualsIgnoreCase,
},
want: want{
query: sq.Eq{"LOWER(test_table.test_col)": "ad\\hurst"},
}, },
}, },
{ {
@@ -1255,11 +1266,11 @@ func TestTextQuery_comp(t *testing.T) {
name: "starts with wildcards", name: "starts with wildcards",
fields: fields{ fields: fields{
Column: testCol, Column: testCol,
Text: "_Hurst%", Text: "_Hur\\st%",
Compare: TextStartsWith, Compare: TextStartsWith,
}, },
want: want{ want: want{
query: sq.Like{"test_table.test_col": "\\_Hurst\\%%"}, query: sq.Like{"test_table.test_col": "\\_Hur\\\\st\\%%"},
}, },
}, },
{ {