File indexing completed on 2025-02-16 03:38:56
0001 /* 0002 This file is part of the KDE Baloo Project 0003 SPDX-FileCopyrightText: 2014 Vishesh Handa <vhanda@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "advancedqueryparser.h" 0009 0010 #include <QTest> 0011 0012 Q_DECLARE_METATYPE(Baloo::Term) 0013 0014 using Term = Baloo::Term; 0015 using AdvancedQueryParser = Baloo::AdvancedQueryParser; 0016 0017 class AdvancedQueryParserTest : public QObject 0018 { 0019 Q_OBJECT 0020 private Q_SLOTS: 0021 0022 void testSimpleProperty(); 0023 void testSimpleString(); 0024 void testStringAndProperty(); 0025 void testLogicalOps(); 0026 void testNesting(); 0027 void testDateTime(); 0028 void testOperators(); 0029 void testBinaryOperatorMissingFirstArg(); 0030 void testNestedParentheses(); 0031 void testNestedParentheses_data(); 0032 void testOptimizedLogic(); 0033 void testOptimizedLogic_data(); 0034 void testPhrases(); 0035 void testPhrases_data(); 0036 void testIncompleteTokens(); 0037 void testIncompleteTokens_data(); 0038 void testQuoting(); 0039 void testQuoting_data(); 0040 void testSpecialCharacters(); 0041 void testSpecialCharacters_data(); 0042 }; 0043 0044 void AdvancedQueryParserTest::testSimpleProperty() 0045 { 0046 AdvancedQueryParser parser; 0047 Term term = parser.parse(QStringLiteral("artist:Coldplay")); 0048 Term expectedTerm(QStringLiteral("artist"), QStringLiteral("Coldplay")); 0049 0050 QCOMPARE(term, expectedTerm); 0051 } 0052 0053 void AdvancedQueryParserTest::testSimpleString() 0054 { 0055 AdvancedQueryParser parser; 0056 Term term = parser.parse(QStringLiteral("Coldplay")); 0057 Term expectedTerm(QString(), QStringLiteral("Coldplay")); 0058 0059 QCOMPARE(term, expectedTerm); 0060 } 0061 0062 void AdvancedQueryParserTest::testStringAndProperty() 0063 { 0064 AdvancedQueryParser parser; 0065 Term term = parser.parse(QStringLiteral("stars artist:Coldplay fire")); 0066 Term expectedTerm(Term::And); 0067 0068 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("stars"))); 0069 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0070 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("fire"))); 0071 0072 QCOMPARE(term, expectedTerm); 0073 } 0074 0075 void AdvancedQueryParserTest::testLogicalOps() 0076 { 0077 // AND 0078 AdvancedQueryParser parser; 0079 Term term = parser.parse(QStringLiteral("artist:Coldplay AND type:song")); 0080 Term expectedTerm(Term::And); 0081 0082 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0083 expectedTerm.addSubTerm(Term(QStringLiteral("type"), QStringLiteral("song"))); 0084 0085 QCOMPARE(term, expectedTerm); 0086 0087 // OR 0088 term = parser.parse(QStringLiteral("artist:Coldplay OR type:song")); 0089 expectedTerm = Term(Term::Or); 0090 0091 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0092 expectedTerm.addSubTerm(Term(QStringLiteral("type"), QStringLiteral("song"))); 0093 0094 QCOMPARE(term, expectedTerm); 0095 0096 // AND then OR 0097 term = parser.parse(QStringLiteral("artist:Coldplay AND type:song OR stars")); 0098 expectedTerm = Term(Term::Or); 0099 0100 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay")) && Term(QStringLiteral("type"), QStringLiteral("song"))); 0101 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("stars"))); 0102 0103 QCOMPARE(term, expectedTerm); 0104 0105 // OR then AND 0106 term = parser.parse(QStringLiteral("artist:Coldplay OR type:song AND stars")); 0107 expectedTerm = Term(Term::And); 0108 0109 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay")) || Term(QStringLiteral("type"), QStringLiteral("song"))); 0110 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("stars"))); 0111 0112 QCOMPARE(term, expectedTerm); 0113 0114 // Multiple ANDs 0115 term = parser.parse(QStringLiteral("artist:Coldplay AND type:song AND stars")); 0116 expectedTerm = Term(Term::And); 0117 0118 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0119 expectedTerm.addSubTerm(Term(QStringLiteral("type"), QStringLiteral("song"))); 0120 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("stars"))); 0121 0122 QCOMPARE(term, expectedTerm); 0123 0124 // Multiple ORs 0125 term = parser.parse(QStringLiteral("artist:Coldplay OR type:song OR stars")); 0126 expectedTerm = Term(Term::Or); 0127 0128 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0129 expectedTerm.addSubTerm(Term(QStringLiteral("type"), QStringLiteral("song"))); 0130 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("stars"))); 0131 0132 QCOMPARE(term, expectedTerm); 0133 } 0134 0135 void AdvancedQueryParserTest::testNesting() 0136 { 0137 AdvancedQueryParser parser; 0138 Term term = parser.parse(QStringLiteral("artist:Coldplay AND (type:song OR stars) fire")); 0139 Term expectedTerm(Term::And); 0140 0141 expectedTerm.addSubTerm(Term(QStringLiteral("artist"), QStringLiteral("Coldplay"))); 0142 expectedTerm.addSubTerm(Term(QStringLiteral("type"), QStringLiteral("song")) || Term(QString(), QStringLiteral("stars"))); 0143 expectedTerm.addSubTerm(Term(QString(), QStringLiteral("fire"))); 0144 0145 QCOMPARE(term, expectedTerm); 0146 } 0147 0148 void AdvancedQueryParserTest::testDateTime() 0149 { 0150 // Integers 0151 AdvancedQueryParser parser; 0152 Term term; 0153 Term expectedTerm; 0154 0155 term = parser.parse(QStringLiteral("modified:2014-12-02")); 0156 expectedTerm = Term(QStringLiteral("modified"), QStringLiteral("2014-12-02")); 0157 QCOMPARE(term, expectedTerm); 0158 0159 term = parser.parse(QStringLiteral("modified:\"2014-12-02T23:22:1\"")); 0160 expectedTerm = Term(QStringLiteral("modified"), QStringLiteral("2014-12-02T23:22:1")); 0161 QCOMPARE(term, expectedTerm); 0162 } 0163 0164 void AdvancedQueryParserTest::testOperators() 0165 { 0166 AdvancedQueryParser parser; 0167 Term term; 0168 Term expectedTerm; 0169 0170 term = parser.parse(QStringLiteral("width:500")); 0171 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::Contains); 0172 QCOMPARE(term, expectedTerm); 0173 0174 term = parser.parse(QStringLiteral("width=500")); 0175 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::Equal); 0176 QCOMPARE(term, expectedTerm); 0177 0178 term = parser.parse(QStringLiteral("width<500")); 0179 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::Less); 0180 QCOMPARE(term, expectedTerm); 0181 0182 term = parser.parse(QStringLiteral("width<=500")); 0183 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::LessEqual); 0184 QCOMPARE(term, expectedTerm); 0185 0186 term = parser.parse(QStringLiteral("width>500")); 0187 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::Greater); 0188 QCOMPARE(term, expectedTerm); 0189 0190 term = parser.parse(QStringLiteral("width>=500")); 0191 expectedTerm = Term(QStringLiteral("width"), QStringLiteral("500"), Term::GreaterEqual); 0192 QCOMPARE(term, expectedTerm); 0193 } 0194 0195 void AdvancedQueryParserTest::testBinaryOperatorMissingFirstArg() 0196 { 0197 AdvancedQueryParser parser; 0198 Term term = parser.parse(QStringLiteral("=:2")); 0199 Term expectedTerm; 0200 QCOMPARE(term, expectedTerm); 0201 } 0202 0203 void AdvancedQueryParserTest::testNestedParentheses() 0204 { 0205 QFETCH(QString, searchInput); 0206 QFETCH(QString, failmessage); 0207 QFETCH(Term, expectedTerm); 0208 0209 AdvancedQueryParser parser; 0210 const auto testTerm = parser.parse(searchInput); 0211 qDebug() << " result term" << testTerm; 0212 qDebug() << "expected term" << expectedTerm; 0213 if (!failmessage.isEmpty()) { 0214 QEXPECT_FAIL("", qPrintable(failmessage), Continue); 0215 } 0216 0217 QCOMPARE(testTerm, expectedTerm); 0218 } 0219 0220 void AdvancedQueryParserTest::testNestedParentheses_data() 0221 { 0222 QTest::addColumn<QString>("searchInput"); 0223 QTest::addColumn<Term>("expectedTerm"); 0224 QTest::addColumn<QString>("failmessage"); 0225 0226 QTest::newRow("a AND b AND c AND d") 0227 << QStringLiteral("a AND b AND c AND d") 0228 << Term{Term::And, QList<Term>{ 0229 Term{QString(), QStringLiteral("a"), Term::Contains}, 0230 Term{QString(), QStringLiteral("b"), Term::Contains}, 0231 Term{QString(), QStringLiteral("c"), Term::Contains}, 0232 Term{QString(), QStringLiteral("d"), Term::Contains}, 0233 }} 0234 << QString() 0235 ; 0236 QTest::newRow("(a AND b) AND (c OR d)") 0237 << QStringLiteral("(a AND b) AND (c OR d)") 0238 << Term{Term::And, QList<Term>{ 0239 Term{QString(), QStringLiteral("a"), Term::Contains}, 0240 Term{QString(), QStringLiteral("b"), Term::Contains}, 0241 Term{Term::Or, QList<Term>{ 0242 Term{QString(), QStringLiteral("c"), Term::Contains}, 0243 Term{QString(), QStringLiteral("d"), Term::Contains}, 0244 }} 0245 }} 0246 << QString() 0247 ; 0248 QTest::newRow("(a AND (b AND (c AND d)))") 0249 << QStringLiteral("(a AND (b AND (c AND d)))") 0250 << Term{Term::And, QList<Term>{ 0251 Term{QString(), QStringLiteral("a"), Term::Contains}, 0252 Term{QString(), QStringLiteral("b"), Term::Contains}, 0253 Term{QString(), QStringLiteral("c"), Term::Contains}, 0254 Term{QString(), QStringLiteral("d"), Term::Contains}, 0255 }} 0256 << QString() 0257 ; 0258 // Test 1 for BUG: 392620 0259 QTest::newRow("a OR ((b AND c) AND d)") 0260 << QStringLiteral("a OR ((b AND c) AND d)") 0261 << Term{Term::Or, QList<Term>{ 0262 Term{QString(), QStringLiteral("a"), Term::Contains}, 0263 Term{Term::And, QList<Term>{ 0264 Term{QString(), QStringLiteral("b"), Term::Contains}, 0265 Term{QString(), QStringLiteral("c"), Term::Contains}, 0266 Term{QString(), QStringLiteral("d"), Term::Contains} 0267 }} 0268 }} 0269 << QString() 0270 ; 0271 // Test 2 for BUG: 392620 0272 QTest::newRow("a AND ((b OR c) OR d)") 0273 << QStringLiteral("a AND ((b OR c) OR d)") 0274 << Term{Term::And, QList<Term>{ 0275 Term{QString(), QStringLiteral("a"), Term::Contains}, 0276 Term{Term::Or, QList<Term>{ 0277 Term{QString(), QStringLiteral("b"), Term::Contains}, 0278 Term{QString(), QStringLiteral("c"), Term::Contains}, 0279 Term{QString(), QStringLiteral("d"), Term::Contains} 0280 }} 0281 }} 0282 << QString(); 0283 ; 0284 } 0285 0286 void AdvancedQueryParserTest::testOptimizedLogic() 0287 { 0288 QFETCH(Term, testTerm); 0289 QFETCH(Term, expectedTerm); 0290 qDebug() << " result term" << testTerm; 0291 qDebug() << "expected term" << expectedTerm; 0292 0293 QCOMPARE(testTerm, expectedTerm); 0294 } 0295 0296 void AdvancedQueryParserTest::testOptimizedLogic_data() 0297 { 0298 QTest::addColumn<Term>("testTerm"); 0299 QTest::addColumn<Term>("expectedTerm"); 0300 0301 // a && b && c && d can be combined into one AND term with 4 subterms 0302 QTest::addRow("a && b && c && d") 0303 << (Term{QString(), QStringLiteral("a"), Term::Contains} 0304 && Term{QString(), QStringLiteral("b"), Term::Contains} 0305 && Term{QString(), QStringLiteral("c"), Term::Contains} 0306 && Term{QString(), QStringLiteral("d"), Term::Contains}) 0307 << Term{Term::And, QList<Term>{ 0308 Term{QString(), QStringLiteral("a"), Term::Contains}, 0309 Term{QString(), QStringLiteral("b"), Term::Contains}, 0310 Term{QString(), QStringLiteral("c"), Term::Contains}, 0311 Term{QString(), QStringLiteral("d"), Term::Contains}, 0312 }} 0313 ; 0314 0315 // (a AND b) AND (c OR d) can be merged as (a AND b AND (c OR D) 0316 QTest::addRow("(a && b) && (c || d)") 0317 << ((Term{QString(), QStringLiteral("a"), Term::Contains} 0318 && Term{QString(), QStringLiteral("b"), Term::Contains}) 0319 && (Term{QString(), QStringLiteral("c"), Term::Contains} 0320 || Term{QString(), QStringLiteral("d"), Term::Contains} 0321 )) 0322 << Term{Term::And, QList<Term>{ 0323 Term{QString(), QStringLiteral("a"), Term::Contains}, 0324 Term{QString(), QStringLiteral("b"), Term::Contains}, 0325 Term{Term::Or, QList<Term>{ 0326 Term{QString(), QStringLiteral("c"), Term::Contains}, 0327 Term{QString(), QStringLiteral("d"), Term::Contains} 0328 }} 0329 }} 0330 ; 0331 } 0332 0333 void AdvancedQueryParserTest::testPhrases() 0334 { 0335 QFETCH(QString, input); 0336 QFETCH(Term, expectedTerm); 0337 0338 AdvancedQueryParser parser; 0339 Term term = parser.parse(input); 0340 QCOMPARE(term, expectedTerm); 0341 } 0342 0343 void AdvancedQueryParserTest::testPhrases_data() 0344 { 0345 QTest::addColumn<QString>("input"); 0346 QTest::addColumn<Term>("expectedTerm"); 0347 0348 auto addRow = [](const QString& input, const Term& term) 0349 { QTest::addRow("%s", qPrintable(input)) << input << term; }; 0350 0351 addRow(QStringLiteral("artist:ColdPlay"), {QStringLiteral("artist"), QStringLiteral("ColdPlay"), Term::Contains}); 0352 addRow(QStringLiteral("artist:\"ColdPlay\""), {QStringLiteral("artist"), QStringLiteral("ColdPlay"), Term::Contains}); 0353 addRow(QStringLiteral("artist:\"Foo Fighters\""), {QStringLiteral("artist"), QStringLiteral("Foo Fighters"), Term::Contains}); 0354 addRow(QStringLiteral("artist:\"Foo Fighters\" OR artist:ColdPlay "), {Term::Or, { 0355 {QStringLiteral("artist"), QStringLiteral("Foo Fighters"), Term::Contains}, 0356 {QStringLiteral("artist"), QStringLiteral("ColdPlay"), Term::Contains}, 0357 }}); 0358 } 0359 0360 void AdvancedQueryParserTest::testIncompleteTokens() 0361 { 0362 QFETCH(QString, input); 0363 QFETCH(Term, expectedTerm); 0364 0365 AdvancedQueryParser parser; 0366 Term term = parser.parse(input); 0367 QCOMPARE(term, expectedTerm); 0368 } 0369 0370 void AdvancedQueryParserTest::testIncompleteTokens_data() 0371 { 0372 QTest::addColumn<QString>("input"); 0373 QTest::addColumn<Term>("expectedTerm"); 0374 0375 auto addRow = [](const QString& name, const QString& input, const Term& term) 0376 { QTest::addRow("%s", qPrintable(name)) << input << term; }; 0377 0378 addRow(QStringLiteral("ends with quote"), QStringLiteral("foo \""), {QString(), QStringLiteral("foo"), Term::Auto}); 0379 addRow(QStringLiteral("ends with comparator"), QStringLiteral("foo>"), {QStringLiteral("foo"), QString(), Term::Contains}); 0380 addRow(QStringLiteral("ends with opening parens"), QStringLiteral("foo ("), {QString(), QStringLiteral("foo")}); 0381 addRow(QStringLiteral("ends with closing parens"), QStringLiteral("foo )"), {QString(), QStringLiteral("foo")}); 0382 } 0383 0384 void AdvancedQueryParserTest::testQuoting() 0385 { 0386 QFETCH(QString, input); 0387 QFETCH(Term, expectedTerm); 0388 0389 AdvancedQueryParser parser; 0390 Term term = parser.parse(input); 0391 QCOMPARE(term, expectedTerm); 0392 } 0393 0394 void AdvancedQueryParserTest::testQuoting_data() 0395 { 0396 QTest::addColumn<QString>("input"); 0397 QTest::addColumn<Term>("expectedTerm"); 0398 0399 auto addRow = [](const QString& name, const QString& input, const Term& term) 0400 { QTest::addRow("%s", qPrintable(name)) << input << term; }; 0401 0402 addRow(QStringLiteral("empty"), QStringLiteral("\"\""), {QString(), QStringLiteral(""), Term::Auto}); 0403 addRow(QStringLiteral("two quoted"), QStringLiteral("\"foo\"\"bar\""), {Term::And, { 0404 {QStringLiteral(""), QStringLiteral("foo"), Term::Contains}, 0405 {QStringLiteral(""), QStringLiteral("bar"), Term::Contains}, 0406 }}); 0407 addRow(QStringLiteral("two quoted properties"), QStringLiteral("artist:\"foo\" OR artist:\"bar\""), {Term::Or, { 0408 {QStringLiteral("artist"), QStringLiteral("foo"), Term::Contains}, 0409 {QStringLiteral("artist"), QStringLiteral("bar"), Term::Contains}, 0410 }}); 0411 addRow(QStringLiteral("two empty properties"), QStringLiteral("artist:\"\" AND filename:\"\""), {Term::And, { 0412 {QStringLiteral("artist"), QStringLiteral(""), Term::Contains}, 0413 {QStringLiteral("filename"), QStringLiteral(""), Term::Contains}, 0414 }}); 0415 addRow(QStringLiteral("quoted and unquoted"), QStringLiteral("one \"two and three\" four"), {Term::And, { 0416 {QStringLiteral(""), QStringLiteral("one"), Term::Contains}, 0417 {QStringLiteral(""), QStringLiteral("two and three"), Term::Contains}, 0418 {QStringLiteral(""), QStringLiteral("four"), Term::Contains}, 0419 }}); 0420 addRow(QStringLiteral("multiple quoted"), QStringLiteral("\"one and two\" \"three and four\""), {Term::And, { 0421 {QStringLiteral(""), QStringLiteral("one and two"), Term::Contains}, 0422 {QStringLiteral(""), QStringLiteral("three and four"), Term::Contains}, 0423 }}); 0424 } 0425 0426 void AdvancedQueryParserTest::testSpecialCharacters() 0427 { 0428 QFETCH(QString, input); 0429 QFETCH(Term, expectedTerm); 0430 0431 AdvancedQueryParser parser; 0432 Term term = parser.parse(input); 0433 QCOMPARE(term, expectedTerm); 0434 } 0435 0436 void AdvancedQueryParserTest::testSpecialCharacters_data() 0437 { 0438 QTest::addColumn<QString>("input"); 0439 QTest::addColumn<Term>("expectedTerm"); 0440 0441 auto addRow = [](const QString& name, const QString& input, const Term& term) 0442 { QTest::addRow("%s", qPrintable(name)) << input << term; }; 0443 0444 addRow(QStringLiteral("mail"), QStringLiteral("foo@bar.com"), 0445 {QStringLiteral(""), QStringLiteral("foo@bar.com"), Term::Contains}); 0446 addRow(QStringLiteral("mail and other"), QStringLiteral("one foo@bar.com two"), {Term::And, { 0447 {QStringLiteral(""), QStringLiteral("one"), Term::Contains}, 0448 {QStringLiteral(""), QStringLiteral("foo@bar.com"), Term::Contains}, 0449 {QStringLiteral(""), QStringLiteral("two"), Term::Contains}, 0450 }}); 0451 addRow(QStringLiteral("filename"), QStringLiteral("foo_bar.png"), 0452 {QStringLiteral(""), QStringLiteral("foo_bar.png"), Term::Contains}); 0453 addRow(QStringLiteral("various phrases"), QStringLiteral("one_two \"foo@bar.com\" two.png"), {Term::And, { 0454 {QStringLiteral(""), QStringLiteral("one_two"), Term::Contains}, 0455 {QStringLiteral(""), QStringLiteral("foo@bar.com"), Term::Contains}, 0456 {QStringLiteral(""), QStringLiteral("two.png"), Term::Contains}, 0457 }}); 0458 } 0459 0460 QTEST_MAIN(AdvancedQueryParserTest) 0461 0462 #include "advancedqueryparsertest.moc"