Skip to content

Commit 2d17b32

Browse files
committed
[BUGFIX] Expression in like escape % and _
The Expression LIKE binary operator does not care about escape % and _ char. No-one has already open an issue about it but in the OGC element PropertyIsLike the user can defined is own wild and single char. This mean that QGIS has to escape % and _ if they are not used as wild and single char.
1 parent 5b79287 commit 2d17b32

File tree

4 files changed

+60
-21
lines changed

4 files changed

+60
-21
lines changed

resources/function_help/json/ILIKE

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@
33
"type": "operator",
44
"description": "Returns 1 if the first parameter matches case-insensitive the supplied pattern. LIKE can be used instead of ILIKE to make the match case-sensitive. Works with numbers also.",
55
"arguments": [
6-
{"arg":"string/number","description":"string to search"},
7-
{"arg":"pattern","description":"pattern to find"}
6+
{"arg":"string/number","description":"string to search"},
7+
{"arg":"pattern","description":"pattern to find, you can use '%' as a wildcard, '_' as a single char and '\\\\' to escape."}
88
],
99
"examples": [
10-
{ "expression":"'A' ILIKE 'A'", "returns":"1"},
11-
{ "expression":"'A' ILIKE 'a'", "returns":"1"},
12-
{ "expression":"'A' ILIKE 'B'", "returns":"0"},
13-
{ "expression":"'ABC' ILIKE 'b'", "returns":"0"},
14-
{ "expression":"'ABC' ILIKE 'B'", "returns":"0"},
15-
{ "expression":"'ABC' ILIKE '%b%'", "returns":"1"},
16-
{ "expression":"'ABC' ILIKE '%B%'", "returns":"1"}
10+
{ "expression":"'A' ILIKE 'A'", "returns":"1"},
11+
{ "expression":"'A' ILIKE 'a'", "returns":"1"},
12+
{ "expression":"'A' ILIKE 'B'", "returns":"0"},
13+
{ "expression":"'ABC' ILIKE 'b'", "returns":"0"},
14+
{ "expression":"'ABC' ILIKE 'B'", "returns":"0"},
15+
{ "expression":"'ABC' ILIKE '_b_'", "returns":"1"},
16+
{ "expression":"'ABC' ILIKE '_B_'", "returns":"1"},
17+
{ "expression":"'ABCD' ILIKE '_b_'", "returns":"0"},
18+
{ "expression":"'ABCD' ILIKE '_B_'", "returns":"0"},
19+
{ "expression":"'ABCD' ILIKE '_b%'", "returns":"1"},
20+
{ "expression":"'ABCD' ILIKE '_B%'", "returns":"1"},
21+
{ "expression":"'ABCD' ILIKE '%b%'", "returns":"1"},
22+
{ "expression":"'ABCD' ILIKE '%B%'", "returns":"1"}
1723
]
1824
}

resources/function_help/json/LIKE

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
"type": "operator",
44
"description": "Returns 1 if the first parameter matches the supplied pattern. Works with numbers also.",
55
"arguments": [
6-
{"arg":"string/number","description":"value"},
7-
{"arg":"pattern","description":"pattern to compare value with"}
6+
{"arg":"string/number","description":"value"},
7+
{"arg":"pattern","description":"pattern to compare value with, you can use '%' as a wildcard, '_' as a single char and '\\\\' to escape."}
88
],
99
"examples": [
10-
{ "expression":"'A' LIKE 'A'", "returns":"1"},
11-
{ "expression":"'A' LIKE 'a'", "returns":"0"},
12-
{ "expression":"'A' LIKE 'B'", "returns":"0"},
13-
{ "expression":"'ABC' LIKE 'B'", "returns":"0"},
14-
{ "expression":"'ABC' LIKE '%B%'", "returns":"1"}
10+
{ "expression":"'A' LIKE 'A'", "returns":"1"},
11+
{ "expression":"'A' LIKE 'a'", "returns":"0"},
12+
{ "expression":"'A' LIKE 'B'", "returns":"0"},
13+
{ "expression":"'ABC' LIKE 'B'", "returns":"0"},
14+
{ "expression":"'ABC' LIKE '_B_'", "returns":"1"},
15+
{ "expression":"'ABCD' LIKE '_B_'", "returns":"0"},
16+
{ "expression":"'ABCD' LIKE '_B%'", "returns":"1"},
17+
{ "expression":"'ABCD' LIKE '%B%'", "returns":"1"},
18+
{ "expression":"'1%' LIKE '1\\\\%'", "returns":"1"},
19+
{ "expression":"'1_' LIKE '1\\\\%'", "returns":"0"}
1520
]
1621
}

src/core/qgsexpression.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,9 +3833,33 @@ QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression *parent, const Q
38333833
if ( mOp == boLike || mOp == boILike || mOp == boNotLike || mOp == boNotILike ) // change from LIKE syntax to regexp
38343834
{
38353835
QString esc_regexp = QRegExp::escape( regexp );
3836-
// XXX escape % and _ ???
3837-
esc_regexp.replace( '%', ".*" );
3838-
esc_regexp.replace( '_', '.' );
3836+
// manage escape % and _
3837+
if ( esc_regexp.startsWith( '%' ) )
3838+
{
3839+
esc_regexp.replace( 0, 1, ".*" );
3840+
}
3841+
QRegExp rx( "[^\\\\](%)" );
3842+
int pos = 0;
3843+
while (( pos = rx.indexIn( esc_regexp, pos ) ) != -1 )
3844+
{
3845+
esc_regexp.replace( pos + 1, 1, ".*" );
3846+
pos += 1;
3847+
}
3848+
rx.setPattern( "\\\\%" );
3849+
esc_regexp.replace( rx, "%" );
3850+
if ( esc_regexp.startsWith( '_' ) )
3851+
{
3852+
esc_regexp.replace( 0, 1, "." );
3853+
}
3854+
rx.setPattern( "[^\\\\](_)" );
3855+
pos = 0;
3856+
while (( pos = rx.indexIn( esc_regexp, pos ) ) != -1 )
3857+
{
3858+
esc_regexp.replace( pos + 1, 1, '.' );
3859+
pos += 1;
3860+
}
3861+
rx.setPattern( "\\\\_" );
3862+
esc_regexp.replace( rx, "_" );
38393863
matches = QRegExp( esc_regexp, mOp == boLike || mOp == boNotLike ? Qt::CaseSensitive : Qt::CaseInsensitive ).exactMatch( str );
38403864
}
38413865
else

tests/src/core/testqgsexpression.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,13 @@ class TestQgsExpression: public QObject
354354

355355
// regexp, like
356356
QTest::newRow( "like 1" ) << "'hello' like '%ll_'" << false << QVariant( 1 );
357-
QTest::newRow( "like 2" ) << "'hello' like 'lo'" << false << QVariant( 0 );
358-
QTest::newRow( "like 3" ) << "'hello' like '%LO'" << false << QVariant( 0 );
357+
QTest::newRow( "like 2" ) << "'hello' like '_el%'" << false << QVariant( 1 );
358+
QTest::newRow( "like 3" ) << "'hello' like 'lo'" << false << QVariant( 0 );
359+
QTest::newRow( "like 4" ) << "'hello' like '%LO'" << false << QVariant( 0 );
359360
QTest::newRow( "ilike" ) << "'hello' ilike '%LO'" << false << QVariant( 1 );
361+
// the \\\\ is like \\ in the interface
362+
QTest::newRow( "like escape 1" ) << "'1%' like '1\\\\%'" << false << QVariant( 1 );
363+
QTest::newRow( "like escape 2" ) << "'1_' like '1\\\\%'" << false << QVariant( 0 );
360364
QTest::newRow( "regexp 1" ) << "'hello' ~ 'll'" << false << QVariant( 1 );
361365
QTest::newRow( "regexp 2" ) << "'hello' ~ '^ll'" << false << QVariant( 0 );
362366
QTest::newRow( "regexp 3" ) << "'hello' ~ 'llo$'" << false << QVariant( 1 );

0 commit comments

Comments
 (0)