diff --git a/app/filter/filtercontroller.cpp b/app/filter/filtercontroller.cpp index a83c56bfc..01fe109ac 100644 --- a/app/filter/filtercontroller.cpp +++ b/app/filter/filtercontroller.cpp @@ -234,7 +234,11 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons QString textValue = QgsExpression::quotedString( filter.value.toList().at( 0 ).toString() ); // remove single quotes from the beginning and end of returned string textValue = textValue.slice( 1, textValue.size() - 2 ); + textValue.replace( QStringLiteral( "!" ), QStringLiteral( "!!" ) ); + textValue.replace( QStringLiteral( "%" ), QStringLiteral( "!%" ) ); + textValue.replace( QStringLiteral( "_" ), QStringLiteral( "!_" ) ); expressionCopy.replace( QStringLiteral( "@@value@@" ), textValue ); + expressionCopy.append( QStringLiteral( " ESCAPE '!'" ) ); break; } case FieldFilter::CheckboxFilter: diff --git a/app/test/testfiltercontroller.cpp b/app/test/testfiltercontroller.cpp index 94f678136..4e3eef7d9 100644 --- a/app/test/testfiltercontroller.cpp +++ b/app/test/testfiltercontroller.cpp @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include @@ -554,11 +556,73 @@ void TestFilterController::testTextFilter() filterValues[filterId] = QVariantList{ QStringLiteral( "great" ) }; mController->processFilters( filterValues ); - const QString expected = QStringLiteral( "(\"Condition\" LIKE '%great%')" ); + const QString expected = QStringLiteral( "(\"Condition\" LIKE '%great%' ESCAPE '!')" ); QCOMPARE( layer->subsetString(), expected ); QCOMPARE( layer->featureCount(), ( long long ) 14 ); } +void TestFilterController::testTextFilterWildcards_data() +{ + QTest::addColumn( "searchValue" ); + QTest::addColumn( "expectedPattern" ); + QTest::addColumn( "expectedCount" ); + + QTest::newRow( "percent" ) << QStringLiteral( "%" ) << QStringLiteral( "%!%%" ) << 2LL; + QTest::newRow( "underscore" ) << QStringLiteral( "_" ) << QStringLiteral( "%!_%" ) << 2LL; + QTest::newRow( "escape-character" ) << QStringLiteral( "!" ) << QStringLiteral( "%!!%" ) << 1LL; + QTest::newRow( "combined-wildcards" ) << QStringLiteral( "%_" ) << QStringLiteral( "%!%!_%" ) << 1LL; +} + +void TestFilterController::testTextFilterWildcards() +{ + QFETCH( QString, searchValue ); + QFETCH( QString, expectedPattern ); + QFETCH( qlonglong, expectedCount ); + + QTemporaryDir tempDir; + QVERIFY( tempDir.isValid() ); + const QString layerPath = tempDir.filePath( QStringLiteral( "roads.gpkg" ) ); + QVERIFY( QFile::copy( TestUtils::testDataDir() + QStringLiteral( "/filtering/roads.gpkg" ), layerPath ) ); + + const QString fieldName = QStringLiteral( "Condition" ); + QgsVectorLayer *layer = new QgsVectorLayer( + layerPath + QStringLiteral( "|layername=roads" ), + QStringLiteral( "text-filter-wildcards" ), + QStringLiteral( "ogr" ) + ); + QVERIFY( layer ); + QVERIFY( layer->isValid() ); + QgsProject::instance()->addMapLayer( layer ); + + const QStringList values = + { + QStringLiteral( "plain" ), + QStringLiteral( "percent%only" ), + QStringLiteral( "under_score" ), + QStringLiteral( "bang!mark" ), + QStringLiteral( "both%_chars" ) + }; + for ( const QString &value : values ) + { + QVERIFY( TestUtils::addFeatureToLayer( layer, fieldName, value ) ); + } + + const QString sql = QStringLiteral( "\"Condition\" LIKE '%@@value@@%'" ); + const QString filterId = TestUtils::setupControllerWithFilter( + mController.get(), FieldFilter::TextFilter, layer->id(), fieldName, sql ); + QVERIFY( !filterId.isEmpty() ); + + QVariantMap filterValues; + filterValues[filterId] = QVariantList{ searchValue }; + mController->processFilters( filterValues ); + + const QString expected = QStringLiteral( "(\"Condition\" LIKE '%1' ESCAPE '!')" ).arg( expectedPattern ); + QCOMPARE( layer->subsetString(), expected ); + QCOMPARE( layer->featureCount(), expectedCount ); + + QgsProject::instance()->removeMapLayer( layer ); +} + // Checkbox filter void TestFilterController::testCheckboxFilter() { diff --git a/app/test/testfiltercontroller.h b/app/test/testfiltercontroller.h index 401616aee..448f5c354 100644 --- a/app/test/testfiltercontroller.h +++ b/app/test/testfiltercontroller.h @@ -56,6 +56,8 @@ class TestFilterController : public QObject // Text filter void testTextFilter(); + void testTextFilterWildcards_data(); + void testTextFilterWildcards(); // Checkbox filter void testCheckboxFilter();