File indexing completed on 2025-01-19 03:53:40
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-03-22 0007 * Description : Building complex database SQL queries from search descriptions 0008 * 0009 * SPDX-FileCopyrightText: 2005 by Renchi Raju <renchi dot raju at gmail dot com> 0010 * SPDX-FileCopyrightText: 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0011 * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "itemquerybuilder_p.h" 0018 0019 // Qt includes 0020 0021 #include <QRegularExpression> 0022 0023 // Local includes 0024 0025 #include "digikam_globals.h" 0026 0027 namespace Digikam 0028 { 0029 0030 ItemQueryBuilder::ItemQueryBuilder() 0031 { 0032 // build a lookup table for month names 0033 0034 for (int i = 1 ; i <= 12 ; ++i) 0035 { 0036 m_shortMonths[i-1] = QLocale().standaloneMonthName(i, QLocale::ShortFormat).toLower(); 0037 m_longMonths[i-1] = QLocale().standaloneMonthName(i, QLocale::LongFormat).toLower(); 0038 } 0039 0040 m_imageTagPropertiesJoined = false; 0041 } 0042 0043 void ItemQueryBuilder::setImageTagPropertiesJoined(bool isJoined) 0044 { 0045 m_imageTagPropertiesJoined = isJoined; 0046 } 0047 0048 QString ItemQueryBuilder::buildQuery(const QString& q, QList<QVariant> *boundValues, ItemQueryPostHooks* const hooks) const 0049 { 0050 // Handle legacy query descriptions 0051 if (q.startsWith(QLatin1String("digikamsearch:"))) 0052 { 0053 return buildQueryFromUrl(QUrl(q), boundValues); 0054 } 0055 else 0056 { 0057 return buildQueryFromXml(q, boundValues, hooks); 0058 } 0059 } 0060 0061 QString ItemQueryBuilder::buildQueryFromXml(const QString& xml, QList<QVariant> *boundValues, ItemQueryPostHooks* const hooks) const 0062 { 0063 SearchXmlCachingReader reader(xml); 0064 QString sql; 0065 bool firstGroup = true; 0066 0067 while (!reader.atEnd()) 0068 { 0069 reader.readNext(); 0070 0071 if (reader.isEndElement()) 0072 { 0073 continue; 0074 } 0075 0076 if (reader.isGroupElement()) 0077 { 0078 addSqlOperator(sql, reader.groupOperator(), firstGroup); 0079 0080 if (firstGroup) 0081 { 0082 firstGroup = false; 0083 } 0084 0085 buildGroup(sql, reader, boundValues, hooks); 0086 } 0087 } 0088 0089 qCDebug(DIGIKAM_DATABASE_LOG) << sql; 0090 0091 return sql; 0092 } 0093 0094 void ItemQueryBuilder::buildGroup(QString& sql, SearchXmlCachingReader& reader, 0095 QList<QVariant> *boundValues, ItemQueryPostHooks* const hooks) const 0096 { 0097 sql += QLatin1String(" ("); 0098 0099 SearchXml::Operator mainGroupOp = reader.groupOperator(); 0100 0101 bool firstField = true; 0102 bool hasContent = false; 0103 0104 while (!reader.atEnd()) 0105 { 0106 reader.readNext(); 0107 0108 if (reader.isEndElement()) 0109 { 0110 break; 0111 } 0112 0113 // subgroup 0114 if (reader.isGroupElement()) 0115 { 0116 hasContent = true; 0117 addSqlOperator(sql, reader.groupOperator(), firstField); 0118 0119 if (firstField) 0120 { 0121 firstField = false; 0122 } 0123 0124 buildGroup(sql, reader, boundValues, hooks); 0125 } 0126 0127 if (reader.isFieldElement()) 0128 { 0129 hasContent = true; 0130 SearchXml::Operator fieldOperator = reader.fieldOperator(); 0131 addSqlOperator(sql, fieldOperator, firstField); 0132 0133 if (firstField) 0134 { 0135 firstField = false; 0136 } 0137 0138 if (!buildField(sql, reader, reader.fieldName(), boundValues, hooks)) 0139 { 0140 addNoEffectContent(sql, fieldOperator); 0141 } 0142 } 0143 } 0144 0145 if (!hasContent) 0146 { 0147 addNoEffectContent(sql, mainGroupOp); 0148 } 0149 0150 sql += QLatin1String(") "); 0151 } 0152 0153 bool ItemQueryBuilder::buildField(QString& sql, SearchXmlCachingReader& reader, const QString& name, 0154 QList<QVariant>* boundValues, ItemQueryPostHooks* const hooks) const 0155 { 0156 SearchXml::Relation relation = reader.fieldRelation(); 0157 FieldQueryBuilder fieldQuery(sql, reader, boundValues, hooks, relation); 0158 0159 // First catch all noeffect fields. Those are only used for message passing when no Signal-Slot-communication is possible 0160 if (name.startsWith(QLatin1String("noeffect_"))) 0161 { 0162 return false; 0163 } 0164 else if (name == QLatin1String("albumid")) 0165 { 0166 if ((relation == SearchXml::Equal) || (relation == SearchXml::Unequal)) 0167 { 0168 fieldQuery.addIntField(QLatin1String("Images.album")); 0169 } 0170 else if (relation == SearchXml::InTree) 0171 { 0172 // see also: CoreDB::getItemNamesInAlbum 0173 QList<int> ids = reader.valueToIntOrIntList(); 0174 0175 if (ids.isEmpty()) 0176 { 0177 qCDebug(DIGIKAM_DATABASE_LOG) << "Relation 'InTree', name 'albumid': No values given"; 0178 return false; 0179 } 0180 0181 sql += QString::fromUtf8("(Images.album IN " 0182 " (SELECT DISTINCT id " 0183 " FROM Albums WHERE "); 0184 bool firstCondition = true; 0185 0186 Q_FOREACH (int albumID, ids) 0187 { 0188 addSqlOperator(sql, SearchXml::Or, firstCondition); 0189 firstCondition = false; 0190 0191 CoreDbAccess access; 0192 int rootId = access.db()->getAlbumRootId(albumID); 0193 QString relativePath = access.db()->getAlbumRelativePath(albumID); 0194 0195 QString childrenWildcard; 0196 0197 if (relativePath == QLatin1String("/")) 0198 { 0199 childrenWildcard = QLatin1String("/%"); 0200 } 0201 else 0202 { 0203 childrenWildcard = relativePath + QLatin1String("/%"); 0204 } 0205 0206 sql += QString::fromUtf8(" ( albumRoot=? AND (relativePath=? OR relativePath LIKE ?) ) "); 0207 *boundValues << rootId << relativePath << childrenWildcard; 0208 } 0209 0210 sql += QLatin1String(" ))"); 0211 } 0212 else if (relation == SearchXml::OneOf) 0213 { 0214 fieldQuery.addChoiceIntField(QLatin1String("Images.album")); 0215 } 0216 } 0217 else if (name == QLatin1String("albumname")) 0218 { 0219 if (CoreDbAccess::parameters().isSQLite()) 0220 { 0221 fieldQuery.addStringField(QLatin1String("Albums.relativePath")); 0222 } 0223 else 0224 { 0225 fieldQuery.addStringField(QLatin1String("Albums.relativePath COLLATE utf8_general_ci")); 0226 } 0227 } 0228 else if (name == QLatin1String("albumcaption")) 0229 { 0230 fieldQuery.addStringField(QLatin1String("Albums.caption")); 0231 } 0232 else if (name == QLatin1String("albumcollection")) 0233 { 0234 fieldQuery.addChoiceStringField(QLatin1String("Albums.collection")); 0235 } 0236 else if ((name == QLatin1String("tagid")) || (name == QLatin1String("labels"))) 0237 { 0238 if (relation == SearchXml::Equal) 0239 { 0240 sql += QString::fromUtf8(" (Images.id IN " 0241 " (SELECT imageid FROM ImageTags " 0242 " WHERE tagid = ?)) "); 0243 *boundValues << reader.valueToInt(); 0244 } 0245 else if (relation == SearchXml::Unequal) 0246 { 0247 sql += QString::fromUtf8(" (Images.id NOT IN " 0248 " (SELECT imageid FROM ImageTags " 0249 " WHERE tagid = ?)) "); 0250 *boundValues << reader.valueToInt(); 0251 } 0252 else if ((relation == SearchXml::InTree) || (relation == SearchXml::NotInTree)) 0253 { 0254 QList<int> ids = reader.valueToIntOrIntList(); 0255 0256 if (ids.isEmpty()) 0257 { 0258 qCDebug(DIGIKAM_DATABASE_LOG) << "Relation 'InTree', name 'tagid': No values given"; 0259 return false; 0260 } 0261 0262 if (relation == SearchXml::InTree) 0263 { 0264 sql += QString::fromUtf8(" (Images.id IN "); 0265 } 0266 else 0267 { 0268 sql += QString::fromUtf8(" (Images.id NOT IN "); 0269 } 0270 0271 sql += QString::fromUtf8(" (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " 0272 " WHERE "); 0273 0274 bool firstCondition = true; 0275 0276 Q_FOREACH (int tagID, ids) 0277 { 0278 addSqlOperator(sql, SearchXml::Or, firstCondition); 0279 firstCondition = false; 0280 sql += QString::fromUtf8(" (TagsTree.pid = ? OR ImageTags.tagid = ? ) "); 0281 *boundValues << tagID << tagID; 0282 } 0283 0284 sql += QString::fromUtf8(" )) "); 0285 } 0286 else if (relation == SearchXml::OneOf) 0287 { 0288 QList<int> values = reader.valueToIntList(); 0289 bool searchForNull = values.removeAll(-1); 0290 sql += QLatin1String(" (Images.id IN ("); 0291 0292 if (searchForNull) 0293 { 0294 sql += QLatin1String(") OR ") + name + QLatin1String(" IS NULL) "); 0295 } 0296 else 0297 { 0298 sql += QString::fromUtf8(" SELECT imageid FROM ImageTags " 0299 " WHERE tagid IN ("); 0300 CoreDB::addBoundValuePlaceholders(sql, values.size()); 0301 sql += QLatin1String("))) "); 0302 } 0303 0304 Q_FOREACH (int tagID, values) 0305 { 0306 *boundValues << tagID; 0307 } 0308 } 0309 else if (relation == SearchXml::AllOf) 0310 { 0311 // there must be an entry in ImageTags for every given tag id 0312 QList<int> ids = reader.valueToIntOrIntList(); 0313 0314 bool firstCondition = true; 0315 0316 Q_FOREACH (int tagID, ids) 0317 { 0318 addSqlOperator(sql, SearchXml::And, firstCondition); 0319 firstCondition = false; 0320 sql += QString::fromUtf8(" (Images.id IN " 0321 " (SELECT imageid FROM ImageTags " 0322 " WHERE tagid = ?)) "); 0323 *boundValues << tagID; 0324 } 0325 } 0326 } 0327 else if (name == QLatin1String("tagname")) 0328 { 0329 QString tagname = QLatin1Char('%') + reader.value() + QLatin1Char('%'); 0330 0331 if (relation == SearchXml::Equal || relation == SearchXml::Like) 0332 { 0333 sql += QString::fromUtf8(" (Images.id IN " 0334 " (SELECT imageid FROM ImageTags " 0335 " WHERE tagid IN " 0336 " (SELECT id FROM Tags WHERE name LIKE ?))) "); 0337 *boundValues << tagname; 0338 } 0339 else if (relation == SearchXml::Unequal || relation == SearchXml::NotLike) 0340 { 0341 sql += QString::fromUtf8(" (Images.id NOT IN " 0342 " (SELECT imageid FROM ImageTags " 0343 " WHERE tagid IN " 0344 " (SELECT id FROM Tags WHERE name LIKE ?))) "); 0345 *boundValues << tagname; 0346 } 0347 else if (relation == SearchXml::InTree) 0348 { 0349 sql += QString::fromUtf8(" (Images.id IN " 0350 " (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " 0351 " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE ?) " 0352 " OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE ?) )) "); 0353 *boundValues << tagname << tagname; 0354 } 0355 else if (relation == SearchXml::NotInTree) 0356 { 0357 sql += QString::fromUtf8(" (Images.id NOT IN " 0358 " (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " 0359 " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE ?) " 0360 " OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE ?) )) "); 0361 *boundValues << tagname << tagname; 0362 } 0363 } 0364 else if (name == QLatin1String("nottagged")) 0365 { 0366 reader.readToEndOfElement(); 0367 sql += QString::fromUtf8(" (Images.id NOT IN (SELECT imageid FROM ImageTags " 0368 " WHERE tagid NOT IN (SELECT id FROM Tags " 0369 " WHERE pid IN (SELECT id FROM Tags " 0370 " WHERE name = '_Digikam_Internal_Tags_') ))) "); 0371 } 0372 else if (name == QLatin1String("notag")) 0373 { 0374 reader.readToEndOfElement(); 0375 sql += QString::fromUtf8(" (Images.id NOT IN " 0376 " (SELECT imageid FROM ImageTags)) "); 0377 } 0378 else if (name == QLatin1String("imageid")) 0379 { 0380 fieldQuery.addLongListField(QLatin1String("Images.id")); 0381 } 0382 else if (name == QLatin1String("filename")) 0383 { 0384 if (CoreDbAccess::parameters().isSQLite()) 0385 { 0386 fieldQuery.addStringField(QLatin1String("Images.name")); 0387 } 0388 else 0389 { 0390 fieldQuery.addStringField(QLatin1String("Images.name COLLATE utf8_general_ci")); 0391 } 0392 } 0393 else if (name == QLatin1String("modificationdate")) 0394 { 0395 fieldQuery.addDateField(QLatin1String("Images.modificationDate")); 0396 } 0397 else if ((name == QLatin1String("filesize")) || 0398 (name == QLatin1String("bytesize"))) 0399 { 0400 fieldQuery.addLongField(QLatin1String("Images.fileSize")); 0401 } 0402 else if (name == QLatin1String("rating")) 0403 { 0404 fieldQuery.addIntField(QLatin1String("ImageInformation.rating")); 0405 } 0406 else if (name == QLatin1String("creationdate")) 0407 { 0408 fieldQuery.addDateField(QLatin1String("ImageInformation.creationDate")); 0409 } 0410 else if (name == QLatin1String("digitizationdate")) 0411 { 0412 fieldQuery.addDateField(QLatin1String("ImageInformation.digitizationDate")); 0413 } 0414 else if (name == QLatin1String("orientation")) 0415 { 0416 fieldQuery.addChoiceIntField(QLatin1String("ImageInformation.orientation")); 0417 } 0418 else if (name == QLatin1String("pageorientation")) 0419 { 0420 if (relation == SearchXml::Equal) 0421 { 0422 int pageOrientation = reader.valueToInt(); 0423 0424 // "1" is landscape, "2" is portrait, "3" is landscape regardless of Exif, "4" is portrait regardless of Exif 0425 if (pageOrientation == 1) 0426 { 0427 sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND ImageInformation.width >= ImageInformation.height) " 0428 " OR (ImageInformation.orientation >= ? AND ImageInformation.width <= ImageInformation.height) ) "); 0429 *boundValues << MetaEngine::ORIENTATION_VFLIP << MetaEngine::ORIENTATION_ROT_90_HFLIP; 0430 } 0431 else if (pageOrientation == 2) 0432 { 0433 sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND ImageInformation.width < ImageInformation.height) " 0434 " OR (ImageInformation.orientation >= ? AND ImageInformation.width > ImageInformation.height) ) "); 0435 *boundValues << MetaEngine::ORIENTATION_VFLIP << MetaEngine::ORIENTATION_ROT_90_HFLIP; 0436 } 0437 else if (pageOrientation == 3 || pageOrientation == 4) 0438 { 0439 // ignoring Exif orientation 0440 sql += QString::fromUtf8(" ( ImageInformation.width "); 0441 ItemQueryBuilder::addSqlRelation(sql, pageOrientation == 3 ? SearchXml::GreaterThanOrEqual : SearchXml::LessThanOrEqual); 0442 sql += QString::fromUtf8(" ImageInformation.height) "); 0443 } 0444 } 0445 } 0446 else if (name == QLatin1String("width")) 0447 { 0448 sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND "); 0449 *boundValues << MetaEngine::ORIENTATION_VFLIP; 0450 fieldQuery.addIntField(QLatin1String("ImageInformation.width")); 0451 sql += QString::fromUtf8(") OR (ImageInformation.orientation >= ? AND "); 0452 *boundValues << MetaEngine::ORIENTATION_ROT_90_HFLIP; 0453 fieldQuery.addIntField(QLatin1String("ImageInformation.height")); 0454 sql += QString::fromUtf8(" ) ) "); 0455 } 0456 else if (name == QLatin1String("height")) 0457 { 0458 sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND "); 0459 *boundValues << MetaEngine::ORIENTATION_VFLIP; 0460 fieldQuery.addIntField(QLatin1String("ImageInformation.height")); 0461 sql += QString::fromUtf8(") OR (ImageInformation.orientation >= ? AND "); 0462 *boundValues << MetaEngine::ORIENTATION_ROT_90_HFLIP; 0463 fieldQuery.addIntField(QLatin1String("ImageInformation.width")); 0464 sql += QString::fromUtf8(" ) ) "); 0465 } 0466 else if (name == QLatin1String("aspectratioimg")) 0467 { 0468 QString query; 0469 QString readerString = (reader.valueToStringOrStringList()).at(0); 0470 0471 if (readerString.contains(QRegularExpression(QLatin1String("^\\d+:\\d+$")))) 0472 { 0473 QStringList ratioNum = readerString.split(QLatin1Char(':'), QT_SKIP_EMPTY_PARTS); 0474 int num = ratioNum.at(0).toInt(); 0475 int denominator = ratioNum.at(1).toInt(); 0476 query = QString::fromUtf8("ABS((ImageInformation.width/CAST(ImageInformation.height AS DOUBLE)) - ?) < 0.1"); 0477 sql += QString::fromUtf8(" (") + query + QString::fromUtf8(") "); 0478 *boundValues << (double)num / denominator; 0479 } 0480 else if (readerString.contains(QRegularExpression(QLatin1String("^\\d+(.\\d+)?$")))) 0481 { 0482 query = QString::fromUtf8("ABS((ImageInformation.width/CAST(ImageInformation.height AS DOUBLE)) - ?) < 0.1"); 0483 sql += QString::fromUtf8(" (") + query + QString::fromUtf8(") "); 0484 *boundValues << readerString.toDouble(); 0485 } 0486 } 0487 else if (name == QLatin1String("pixelsize")) 0488 { 0489 fieldQuery.addIntField(QLatin1String("(ImageInformation.width * ImageInformation.height)")); 0490 } 0491 else if (name == QLatin1String("pixels")) 0492 { 0493 fieldQuery.addIntField(QLatin1String("(ImageInformation.width * ImageInformation.height)")); 0494 } 0495 else if (name == QLatin1String("format")) 0496 { 0497 fieldQuery.addChoiceStringField(QLatin1String("ImageInformation.format")); 0498 } 0499 else if (name == QLatin1String("colordepth")) 0500 { 0501 fieldQuery.addIntField(QLatin1String("ImageInformation.colorDepth")); 0502 } 0503 else if (name == QLatin1String("colormodel")) 0504 { 0505 fieldQuery.addIntField(QLatin1String("ImageInformation.colorModel")); 0506 } 0507 else if (name == QLatin1String("videoaspectratio")) 0508 { 0509 if (relation == SearchXml::OneOf) 0510 { 0511 QStringList values = reader.valueToStringList(); 0512 0513 if (values.isEmpty()) 0514 { 0515 qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; 0516 return false; 0517 } 0518 0519 QList<double> ratioValues; 0520 0521 Q_FOREACH (const QString& value, values) 0522 { 0523 *boundValues << value; 0524 0525 if (value.contains(QLatin1Char(':'))) 0526 { 0527 QStringList ratioNum = value.split(QLatin1Char(':'), QT_SKIP_EMPTY_PARTS); 0528 int num = ratioNum.at(0).toInt(); 0529 int denominator = ratioNum.at(1).toInt(); 0530 ratioValues << (double)num / denominator; 0531 } 0532 } 0533 0534 sql += QString::fromUtf8("(VideoMetadata.aspectRatio IN ("); 0535 CoreDB::addBoundValuePlaceholders(sql, values.size()); 0536 sql += QString::fromUtf8(") "); 0537 QString query = QString::fromUtf8("ABS((CAST(VideoMetadata.aspectRatio AS DOUBLE) - ?) < 0.1) "); 0538 0539 Q_FOREACH (double value, ratioValues) 0540 { 0541 *boundValues << value; 0542 sql += QString::fromUtf8("OR ") + query; 0543 } 0544 0545 sql += QString::fromUtf8(") "); 0546 } 0547 else 0548 { 0549 QString value = reader.value(); 0550 *boundValues << value; 0551 0552 if (value.contains(QLatin1Char(':'))) 0553 { 0554 QStringList ratioNum = value.split(QLatin1Char(':'), QT_SKIP_EMPTY_PARTS); 0555 int num = ratioNum.at(0).toInt(); 0556 int denominator = ratioNum.at(1).toInt(); 0557 *boundValues << (double)num / denominator; 0558 } 0559 0560 sql += QString::fromUtf8("(VideoMetadata.aspectRatio=? OR ABS((CAST(VideoMetadata.aspectRatio AS DOUBLE) - ?) < 0.1 )) "); 0561 } 0562 } 0563 else if (name == QLatin1String("videoaudiobitrate")) 0564 { 0565 //fieldQuery.addIntField("VideoMetadata.audioBitRate"); 0566 QList<int> values = reader.valueToIntList(); 0567 0568 if (values.size() != 2) 0569 { 0570 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 0571 return false; 0572 } 0573 0574 sql += QString::fromUtf8(" ( CAST(VideoMetadata.audioBitRate AS INTEGER)"); 0575 ItemQueryBuilder::addSqlRelation(sql, 0576 relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual 0577 : SearchXml::GreaterThan); 0578 sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.audioBitRate AS INTEGER)"); 0579 ItemQueryBuilder::addSqlRelation(sql, 0580 relation == SearchXml::Interval ? SearchXml::LessThanOrEqual 0581 : SearchXml::LessThan); 0582 sql += QString::fromUtf8(" ?) "); 0583 0584 *boundValues << values.first() << values.last(); 0585 } 0586 else if (name == QLatin1String("videoaudiochanneltype")) 0587 { 0588 if (relation == SearchXml::OneOf) 0589 { 0590 QStringList values = reader.valueToStringList(); 0591 0592 if (values.isEmpty()) 0593 { 0594 qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; 0595 return false; 0596 } 0597 0598 Q_FOREACH (const QString& value, values) 0599 { 0600 *boundValues << value; 0601 0602 if (value == QLatin1String("1")) 0603 { 0604 *boundValues << QLatin1String("Mono"); 0605 } 0606 else if (value == QLatin1String("2")) 0607 { 0608 *boundValues << QLatin1String("Stereo"); 0609 } 0610 } 0611 0612 sql += QString::fromUtf8("(VideoMetadata.audioChannelType IN ("); 0613 CoreDB::addBoundValuePlaceholders(sql, boundValues->size()); 0614 sql += QString::fromUtf8(")) "); 0615 } 0616 else 0617 { 0618 QString value = reader.value(); 0619 *boundValues << value; 0620 0621 if (value == QLatin1String("1")) 0622 { 0623 *boundValues << QLatin1String("Mono"); 0624 } 0625 else if (value == QLatin1String("2")) 0626 { 0627 *boundValues << QLatin1String("Stereo"); 0628 } 0629 0630 sql += QString::fromUtf8("(VideoMetadata.audioChannelType IN ("); 0631 CoreDB::addBoundValuePlaceholders(sql, boundValues->size()); 0632 sql += QString::fromUtf8(")) "); 0633 } 0634 } 0635 else if (name == QLatin1String("videoaudioCodec")) 0636 { 0637 fieldQuery.addChoiceStringField(QLatin1String("VideoMetadata.audioCompressor")); 0638 } 0639 else if (name == QLatin1String("videoduration")) 0640 { 0641 QList<int> values = reader.valueToIntList(); 0642 0643 if (values.size() != 2) 0644 { 0645 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 0646 return false; 0647 } 0648 0649 sql += QString::fromUtf8(" ( CAST(VideoMetadata.duration AS INTEGER)"); 0650 ItemQueryBuilder::addSqlRelation(sql, 0651 relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual 0652 : SearchXml::GreaterThan); 0653 sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.duration AS INTEGER)"); 0654 ItemQueryBuilder::addSqlRelation(sql, 0655 relation == SearchXml::Interval ? SearchXml::LessThanOrEqual 0656 : SearchXml::LessThan); 0657 sql += QString::fromUtf8(" ?) "); 0658 0659 *boundValues << values.first()*1000 << values.last()*1000; 0660 } 0661 else if (name == QLatin1String("videoframerate")) 0662 { 0663 //fieldQuery.addChoiceStringField("VideoMetadata.frameRate"); 0664 QList<double> values = reader.valueToDoubleList(); 0665 0666 if (values.size() != 2) 0667 { 0668 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 0669 return false; 0670 } 0671 0672 sql += QString::fromUtf8(" ( CAST(VideoMetadata.frameRate AS DOUBLE)"); 0673 ItemQueryBuilder::addSqlRelation(sql, 0674 relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual 0675 : SearchXml::GreaterThan); 0676 sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.frameRate AS DOUBLE)"); 0677 ItemQueryBuilder::addSqlRelation(sql, 0678 relation == SearchXml::Interval ? SearchXml::LessThanOrEqual 0679 : SearchXml::LessThan); 0680 sql += QString::fromUtf8(" ?) "); 0681 0682 *boundValues << values.first() << values.last(); 0683 } 0684 else if (name == QLatin1String("videocodec")) 0685 { 0686 if (relation == SearchXml::OneOf) 0687 { 0688 QStringList values = reader.valueToStringList(); 0689 0690 if (values.isEmpty()) 0691 { 0692 qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; 0693 return false; 0694 } 0695 0696 Q_FOREACH (const QString& value, values) 0697 { 0698 sql += QString::fromUtf8("( Upper(VideoMetadata.videoCodec) LIKE '%") + value.toUpper() + QString::fromUtf8("%' "); 0699 0700 if (value != values.last()) 0701 { 0702 sql += QString::fromUtf8("OR "); 0703 } 0704 } 0705 0706 sql += QString::fromUtf8(") "); 0707 } 0708 else 0709 { 0710 QString value = reader.value(); 0711 sql += QString::fromUtf8("(Upper(VideoMetadata.videoCodec) LIKE '%") + value.toUpper() + QString::fromUtf8("%') "); 0712 } 0713 } 0714 else if (name == QLatin1String("make")) 0715 { 0716 fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.make")); 0717 } 0718 else if (name == QLatin1String("model")) 0719 { 0720 fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.model")); 0721 } 0722 else if (name == QLatin1String("lenses")) 0723 { 0724 fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.lens")); 0725 } 0726 else if (name == QLatin1String("aperture")) 0727 { 0728 fieldQuery.addDoubleField(QLatin1String("ImageMetadata.aperture")); 0729 } 0730 else if (name == QLatin1String("focallength")) 0731 { 0732 fieldQuery.addDoubleField(QLatin1String("ImageMetadata.focalLength")); 0733 } 0734 else if (name == QLatin1String("focallength35")) 0735 { 0736 fieldQuery.addDoubleField(QLatin1String("ImageMetadata.focalLength35")); 0737 } 0738 else if (name == QLatin1String("exposuretime")) 0739 { 0740 fieldQuery.addDoubleField(QLatin1String("ImageMetadata.exposureTime")); 0741 } 0742 else if (name == QLatin1String("exposureprogram")) 0743 { 0744 fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.exposureProgram")); 0745 } 0746 else if (name == QLatin1String("exposuremode")) 0747 { 0748 fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.exposureMode")); 0749 } 0750 else if (name == QLatin1String("sensitivity")) 0751 { 0752 fieldQuery.addIntField(QLatin1String("ImageMetadata.sensitivity")); 0753 } 0754 else if (name == QLatin1String("flashmode")) 0755 { 0756 fieldQuery.addIntBitmaskField(QLatin1String("ImageMetadata.flash")); 0757 } 0758 else if (name == QLatin1String("whitebalance")) 0759 { 0760 fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.whiteBalance")); 0761 } 0762 else if (name == QLatin1String("whitebalancecolortemperature")) 0763 { 0764 fieldQuery.addIntField(QLatin1String("ImageMetadata.whiteBalanceColorTemperature")); 0765 } 0766 else if (name == QLatin1String("meteringmode")) 0767 { 0768 fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.meteringMode")); 0769 } 0770 else if (name == QLatin1String("subjectdistance")) 0771 { 0772 fieldQuery.addDoubleField(QLatin1String("ImageMetadata.subjectDistance")); 0773 } 0774 else if (name == QLatin1String("subjectdistancecategory")) 0775 { 0776 fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.subjectDistanceCategory")); 0777 } 0778 else if (name == QLatin1String("position")) 0779 { 0780 fieldQuery.addPosition(); 0781 } 0782 else if (name == QLatin1String("latitude")) 0783 { 0784 fieldQuery.addDoubleField(QLatin1String("ImagePositions.latitudeNumber")); 0785 } 0786 else if (name == QLatin1String("longitude")) 0787 { 0788 fieldQuery.addDoubleField(QLatin1String("ImagePositions.longitudeNumber")); 0789 } 0790 else if (name == QLatin1String("altitude")) 0791 { 0792 fieldQuery.addDoubleField(QLatin1String("ImagePositions.altitude")); 0793 } 0794 else if (name == QLatin1String("positionorientation")) 0795 { 0796 fieldQuery.addDoubleField(QLatin1String("ImagePositions.orientation")); 0797 } 0798 else if (name == QLatin1String("positiontilt")) 0799 { 0800 fieldQuery.addDoubleField(QLatin1String("ImagePositions.tilt")); 0801 } 0802 else if (name == QLatin1String("positionroll")) 0803 { 0804 fieldQuery.addDoubleField(QLatin1String("ImagePositions.roll")); 0805 } 0806 else if (name == QLatin1String("positiondescription")) 0807 { 0808 fieldQuery.addStringField(QLatin1String("ImagePositions.description")); 0809 } 0810 else if (name == QLatin1String("nogps")) 0811 { 0812 sql += QString::fromUtf8(" (ImagePositions.latitudeNumber IS NULL AND ImagePositions.longitudeNumber IS NULL) "); 0813 } 0814 else if ((name == QLatin1String("provinceState")) || 0815 (name == QLatin1String("location")) || 0816 (name == QLatin1String("country")) || 0817 (name == QLatin1String("city"))) 0818 { 0819 if (relation == SearchXml::OneOf) 0820 { 0821 QStringList values = reader.valueToStringList(); 0822 0823 if (values.isEmpty()) 0824 { 0825 qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; 0826 return false; 0827 } 0828 0829 sql += QLatin1String(" (Images.id IN (SELECT imageid FROM ImageProperties "); 0830 sql += QLatin1String("WHERE ImageProperties.property = ? AND ImageProperties.value IN ("); 0831 CoreDB::addBoundValuePlaceholders(sql, values.size()); 0832 *boundValues << name; 0833 0834 Q_FOREACH (const QString& value, values) 0835 { 0836 *boundValues << value; 0837 } 0838 0839 sql += QLatin1String(") ) ) "); 0840 } 0841 else 0842 { 0843 QString value = reader.value(); 0844 sql += QLatin1String(" (Images.id IN (SELECT imageid FROM ImageProperties "); 0845 sql += QLatin1String("WHERE ImageProperties.property = ? AND ImageProperties.value = ?) ) "); 0846 *boundValues << name << value; 0847 } 0848 } 0849 else if (name == QLatin1String("creator")) 0850 { 0851 sql += QString::fromUtf8(" (Images.id IN " 0852 " (SELECT imageid FROM ImageCopyright " 0853 " WHERE property='creator' AND value "); 0854 ItemQueryBuilder::addSqlRelation(sql, relation); 0855 sql += QString::fromUtf8(" ?)) "); 0856 *boundValues << fieldQuery.prepareForLike(reader.value()); 0857 } 0858 else if (name == QLatin1String("comment")) 0859 { 0860 sql += QString::fromUtf8(" (Images.id IN " 0861 " (SELECT imageid FROM ImageComments " 0862 " WHERE type=? AND comment "); 0863 ItemQueryBuilder::addSqlRelation(sql, relation); 0864 sql += QString::fromUtf8(" ?)) "); 0865 *boundValues << DatabaseComment::Comment << fieldQuery.prepareForLike(reader.value()); 0866 } 0867 else if (name == QLatin1String("commentauthor")) 0868 { 0869 sql += QString::fromUtf8(" (Images.id IN " 0870 " (SELECT imageid FROM ImageComments " 0871 " WHERE type=? AND author "); 0872 ItemQueryBuilder::addSqlRelation(sql, relation); 0873 sql += QString::fromUtf8(" ?)) "); 0874 *boundValues << DatabaseComment::Comment << fieldQuery.prepareForLike(reader.value()); 0875 } 0876 else if (name == QLatin1String("headline")) 0877 { 0878 sql += QString::fromUtf8(" (Images.id IN " 0879 " (SELECT imageid FROM ImageComments " 0880 " WHERE type=? AND comment "); 0881 ItemQueryBuilder::addSqlRelation(sql, relation); 0882 sql += QString::fromUtf8(" ?)) "); 0883 *boundValues << DatabaseComment::Headline << fieldQuery.prepareForLike(reader.value()); 0884 } 0885 else if (name == QLatin1String("title")) 0886 { 0887 sql += QString::fromUtf8(" (Images.id IN " 0888 " (SELECT imageid FROM ImageComments " 0889 " WHERE type=? AND comment "); 0890 ItemQueryBuilder::addSqlRelation(sql, relation); 0891 sql += QString::fromUtf8(" ?)) "); 0892 *boundValues << DatabaseComment::Title << fieldQuery.prepareForLike(reader.value()); 0893 } 0894 else if (name == QLatin1String("emptytext")) 0895 { 0896 if ((relation == SearchXml::OneOf) || (relation == SearchXml::Equal)) 0897 { 0898 QStringList values; 0899 0900 if (relation == SearchXml::OneOf) 0901 { 0902 values = reader.valueToStringList(); 0903 } 0904 else 0905 { 0906 values << reader.value(); 0907 } 0908 0909 if (values.isEmpty()) 0910 { 0911 qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf or Equal is empty"; 0912 return false; 0913 } 0914 0915 if (values.contains(QLatin1String("creator"))) 0916 { 0917 sql += QString::fromUtf8(" (Images.id NOT IN " 0918 " (SELECT imageid FROM ImageCopyright " 0919 " WHERE property='creator' AND value != '')) "); 0920 0921 values.removeAll(QLatin1String("creator")); 0922 0923 if (!values.isEmpty()) 0924 { 0925 sql += QString::fromUtf8("OR"); 0926 } 0927 } 0928 0929 if (!values.isEmpty()) 0930 { 0931 sql += QString::fromUtf8(" (Images.id NOT IN " 0932 " (SELECT imageid FROM ImageComments WHERE "); 0933 0934 Q_FOREACH (const QString& value, values) 0935 { 0936 if (value == QLatin1String("headline")) 0937 { 0938 sql += QString::fromUtf8("(type=? AND comment != '') "); 0939 *boundValues << DatabaseComment::Headline; 0940 } 0941 else if (value == QLatin1String("comment")) 0942 { 0943 sql += QString::fromUtf8("(type=? AND comment != '') "); 0944 *boundValues << DatabaseComment::Comment; 0945 } 0946 else if (value == QLatin1String("title")) 0947 { 0948 sql += QString::fromUtf8("(type=? AND comment != '') "); 0949 *boundValues << DatabaseComment::Title; 0950 } 0951 else if (value == QLatin1String("author")) 0952 { 0953 sql += QString::fromUtf8("(type=? AND author != '') "); 0954 *boundValues << DatabaseComment::Comment; 0955 } 0956 0957 if (value != values.last()) 0958 { 0959 sql += QString::fromUtf8("OR "); 0960 } 0961 } 0962 0963 sql += QString::fromUtf8(")) "); 0964 } 0965 } 0966 } 0967 else if (name == QLatin1String("monthday")) 0968 { 0969 if (relation == SearchXml::Equal) 0970 { 0971 QList<int> values = reader.valueToIntList(); 0972 0973 if (values.size() != 2) 0974 { 0975 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 0976 return false; 0977 } 0978 0979 // to extract a part of the date we need different SQL code for SQLite and MySQL 0980 0981 if (CoreDbAccess::parameters().isSQLite()) 0982 { 0983 if ((values.at(0) > 0) && (values.at(1) > 0)) 0984 { 0985 sql += QString::fromUtf8(" (STRFTIME('%m%d', ImageInformation.creationDate) = ?) "); 0986 QString date = QString::number(values.at(0)).rightJustified(2, QLatin1Char('0')); 0987 date += QString::number(values.at(1)).rightJustified(2, QLatin1Char('0')); 0988 *boundValues << date; 0989 } 0990 else if (values.at(0) > 0) 0991 { 0992 sql += QString::fromUtf8(" (STRFTIME('%m', ImageInformation.creationDate) = ?) "); 0993 *boundValues << QString::number(values.at(0)).rightJustified(2, QLatin1Char('0')); 0994 } 0995 else 0996 { 0997 sql += QString::fromUtf8(" (STRFTIME('%d', ImageInformation.creationDate) = ?) "); 0998 *boundValues << QString::number(values.at(1)).rightJustified(2, QLatin1Char('0')); 0999 } 1000 } 1001 else 1002 { 1003 if (values.at(0) > 0) 1004 { 1005 sql += QString::fromUtf8(" (MONTH(ImageInformation.creationDate) = ?"); 1006 *boundValues << values.at(0); 1007 } 1008 1009 if ((values.at(0) > 0) && (values.at(1) > 0)) 1010 { 1011 sql += QString::fromUtf8(" AND DAY(ImageInformation.creationDate) = ?"); 1012 *boundValues << values.at(1); 1013 } 1014 else if (values.at(1) > 0) 1015 { 1016 sql += QString::fromUtf8(" (DAY(ImageInformation.creationDate) = ?"); 1017 *boundValues << values.at(1); 1018 } 1019 1020 sql += QString::fromUtf8(") "); 1021 } 1022 } 1023 } 1024 else if (name == QLatin1String("creationtime")) 1025 { 1026 if (relation == SearchXml::Interval) 1027 { 1028 QList<QDateTime> values = reader.valueToDateTimeList(); 1029 1030 if (values.size() != 2) 1031 { 1032 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 1033 return false; 1034 } 1035 1036 // to extract a part of the date we need different SQL code for SQLite and MySQL 1037 1038 if (CoreDbAccess::parameters().isSQLite()) 1039 { 1040 sql += QString::fromUtf8(" (STRFTIME('%H:%M:%S', ImageInformation.creationDate) BETWEEN ? AND ?) "); 1041 *boundValues << values.at(0).time() << values.at(1).time(); 1042 } 1043 else 1044 { 1045 sql += QString::fromUtf8(" (TIME(ImageInformation.creationDate) BETWEEN ? AND ?) "); 1046 *boundValues << values.at(0).time() << values.at(1).time(); 1047 } 1048 } 1049 } 1050 else if (name == QLatin1String("imagetagproperty")) 1051 { 1052 if (relation == SearchXml::Equal || relation == SearchXml::InTree) 1053 { 1054 // First, read attributes 1055 QStringView tagAttribute = reader.attributes().value(QLatin1String("tagid")); 1056 int tagId = 0; 1057 1058 if (!tagAttribute.isEmpty()) 1059 { 1060 tagId = tagAttribute.toString().toInt(); 1061 } 1062 1063 // read values: one or two strings 1064 QStringList values = reader.valueToStringOrStringList(); 1065 1066 if ((values.size() < 1) || (values.size() > 2)) 1067 { 1068 qCDebug(DIGIKAM_DATABASE_LOG) << "The imagetagproperty field requires one value (property) or two values (property, value)."; 1069 return false; 1070 } 1071 1072 QString selectQuery; 1073 // %1 is resolved to either "ImageTagProperties." or the empty string 1074 if (tagId) 1075 { 1076 if (relation == SearchXml::Equal) 1077 { 1078 selectQuery += QString::fromUtf8("%1tagid=? AND "); 1079 *boundValues << tagId; 1080 } 1081 else // InTree 1082 { 1083 selectQuery += QString::fromUtf8("(%1tagid=? OR %1tagid IN (SELECT id FROM TagsTree WHERE pid=?)) AND "); 1084 *boundValues << tagId << tagId; 1085 } 1086 } 1087 1088 if (values.size() == 1) 1089 { 1090 selectQuery += QString::fromUtf8("%1property=? "); 1091 *boundValues << values.first(); 1092 } 1093 else if (values.size() == 2) 1094 { 1095 selectQuery += QString::fromUtf8("%1property=? AND %1value "); 1096 ItemQueryBuilder::addSqlRelation(selectQuery, relation); 1097 selectQuery += QString::fromUtf8(" ? "); 1098 *boundValues << values.at(0) << fieldQuery.prepareForLike(values.at(1)); 1099 } 1100 1101 // This indicates that the ImageTagProperties is joined in the SELECT query, 1102 // so one entry is listed for each property entry (not for each image id) 1103 if (m_imageTagPropertiesJoined) 1104 { 1105 sql += QString::fromUtf8(" ( "); 1106 sql += selectQuery.arg(QString::fromUtf8("ImageTagProperties.")); 1107 sql += QString::fromUtf8(" ) "); 1108 } 1109 else 1110 { 1111 sql += QString::fromUtf8(" (Images.id IN " 1112 " (SELECT imageid FROM ImageTagProperties WHERE "); 1113 sql += selectQuery.arg(QString()); 1114 sql += QString::fromUtf8(" )) "); 1115 } 1116 } 1117 } 1118 else if (name == QLatin1String("keyword")) 1119 { 1120 // keyword is the common search in the text fields 1121 1122 sql += QLatin1String(" ( "); 1123 1124 addSqlOperator(sql, SearchXml::Or, true); 1125 buildField(sql, reader, QLatin1String("albumname"), boundValues, hooks); 1126 1127 addSqlOperator(sql, SearchXml::Or, false); 1128 buildField(sql, reader, QLatin1String("filename"), boundValues, hooks); 1129 1130 addSqlOperator(sql, SearchXml::Or, false); 1131 buildField(sql, reader, QLatin1String("tagname"), boundValues, hooks); 1132 1133 addSqlOperator(sql, SearchXml::Or, false); 1134 buildField(sql, reader, QLatin1String("albumcaption"), boundValues, hooks); 1135 1136 addSqlOperator(sql, SearchXml::Or, false); 1137 buildField(sql, reader, QLatin1String("albumcollection"), boundValues, hooks); 1138 1139 addSqlOperator(sql, SearchXml::Or, false); 1140 buildField(sql, reader, QLatin1String("comment"), boundValues, hooks); 1141 1142 addSqlOperator(sql, SearchXml::Or, false); 1143 buildField(sql, reader, QLatin1String("title"), boundValues, hooks); 1144 1145 sql += QLatin1String(" ) "); 1146 } 1147 else if (name == QLatin1String("faceregionscount")) 1148 { 1149 sql += QString::fromUtf8(" (Images.id IN (SELECT imageid FROM ImageTagProperties " 1150 " WHERE property = ? OR property = ? " 1151 " GROUP BY imageid HAVING COUNT(*) "); 1152 1153 if ((relation == SearchXml::Interval) || (relation == SearchXml::IntervalOpen)) 1154 { 1155 QList<int> values = reader.valueToIntList(); 1156 1157 if (values.size() != 2) 1158 { 1159 qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; 1160 return false; 1161 } 1162 1163 ItemQueryBuilder::addSqlRelation(sql, 1164 relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual 1165 : SearchXml::GreaterThan); 1166 sql += QString::fromUtf8(" ? AND COUNT(*) "); 1167 ItemQueryBuilder::addSqlRelation(sql, 1168 relation == SearchXml::Interval ? SearchXml::LessThanOrEqual 1169 : SearchXml::LessThan); 1170 sql += QString::fromUtf8(" ?) ) "); 1171 *boundValues << ImageTagPropertyName::tagRegion() << ImageTagPropertyName::autodetectedFace() 1172 << values.first() << values.last(); 1173 } 1174 else 1175 { 1176 ItemQueryBuilder::addSqlRelation(sql, relation); 1177 sql += QString::fromUtf8(" ?) ) "); 1178 *boundValues << ImageTagPropertyName::tagRegion() << ImageTagPropertyName::autodetectedFace() 1179 << reader.valueToInt(); 1180 } 1181 } 1182 else if (name == QLatin1String("nofaceregions")) 1183 { 1184 reader.readToEndOfElement(); 1185 sql += QString::fromUtf8(" (Images.id NOT IN (SELECT imageid FROM ImageTagProperties " 1186 " WHERE property = ? OR property = ? ) ) "); 1187 *boundValues << ImageTagPropertyName::tagRegion() << ImageTagPropertyName::autodetectedFace(); 1188 } 1189 else if (name == QLatin1String("similarity")) 1190 { 1191 qCWarning(DIGIKAM_DATABASE_LOG) << "Search field \"similarity\" is not supported by ItemQueryBuilder"; 1192 } 1193 else 1194 { 1195 qCDebug(DIGIKAM_DATABASE_LOG) << "Search field" << name << "not known by this version of ItemQueryBuilder"; 1196 return false; 1197 } 1198 1199 return true; 1200 } 1201 1202 void ItemQueryBuilder::addSqlOperator(QString& sql, SearchXml::Operator op, bool isFirst) 1203 { 1204 if (isFirst) 1205 { 1206 if ((op == SearchXml::AndNot) || (op == SearchXml::OrNot)) 1207 { 1208 sql += QLatin1String("NOT"); 1209 } 1210 1211 return; 1212 } 1213 1214 switch (op) 1215 { 1216 case SearchXml::And: 1217 sql += QLatin1String("AND"); 1218 break; 1219 case SearchXml::Or: 1220 sql += QLatin1String("OR"); 1221 break; 1222 case SearchXml::AndNot: 1223 sql += QLatin1String("AND NOT"); 1224 break; 1225 case SearchXml::OrNot: 1226 sql += QLatin1String("OR NOT"); 1227 break; 1228 } 1229 } 1230 1231 void ItemQueryBuilder::addSqlRelation(QString& sql, SearchXml::Relation rel) 1232 { 1233 switch (rel) 1234 { 1235 default: 1236 case SearchXml::Equal: 1237 sql += QLatin1Char('='); 1238 break; 1239 case SearchXml::Unequal: 1240 sql += QLatin1String("<>"); 1241 break; 1242 case SearchXml::Like: 1243 sql += QLatin1String("LIKE"); 1244 break; 1245 case SearchXml::NotLike: 1246 sql += QLatin1String("NOT LIKE"); 1247 break; 1248 case SearchXml::LessThan: 1249 sql += QLatin1Char('<'); 1250 break; 1251 case SearchXml::GreaterThan: 1252 sql += QLatin1Char('>'); 1253 break; 1254 case SearchXml::LessThanOrEqual: 1255 sql += QLatin1String("<="); 1256 break; 1257 case SearchXml::GreaterThanOrEqual: 1258 sql += QLatin1String(">="); 1259 break; 1260 case SearchXml::OneOf: 1261 sql += QLatin1String("IN"); 1262 break; 1263 } 1264 } 1265 1266 void ItemQueryBuilder::addNoEffectContent(QString& sql, SearchXml::Operator op) 1267 { 1268 // add a condition statement with no effect 1269 switch (op) 1270 { 1271 case SearchXml::And: 1272 case SearchXml::Or: 1273 sql += QLatin1String(" 1 "); 1274 break; 1275 case SearchXml::AndNot: 1276 case SearchXml::OrNot: 1277 sql += QLatin1String(" 0 "); 1278 break; 1279 } 1280 } 1281 1282 QString ItemQueryBuilder::convertFromUrlToXml(const QUrl& url) const 1283 { 1284 int count = QUrlQuery(url).queryItemValue(QLatin1String("count")).toInt(); 1285 1286 if (count <= 0) 1287 { 1288 return QString(); 1289 } 1290 1291 QMap<int, RuleTypeForConversion> rulesMap; 1292 1293 for (int i = 1 ; i <= count ; ++i) 1294 { 1295 RuleTypeForConversion rule; 1296 1297 QString key = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".key")).toLower(); 1298 QString op = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".op")).toLower(); 1299 1300 if (key == QLatin1String("album")) 1301 { 1302 rule.key = QLatin1String("albumid"); 1303 } 1304 else if (key == QLatin1String("imagename")) 1305 { 1306 rule.key = QLatin1String("filename"); 1307 } 1308 else if (key == QLatin1String("imagecaption")) 1309 { 1310 rule.key = QLatin1String("comment"); 1311 } 1312 else if (key == QLatin1String("imagedate")) 1313 { 1314 rule.key = QLatin1String("creationdate"); 1315 } 1316 else if (key == QLatin1String("tag")) 1317 { 1318 rule.key = QLatin1String("tagid"); 1319 } 1320 else 1321 { 1322 // other field names did not change: 1323 // albumname, albumcaption, albumcollection, tagname, keyword, rating 1324 rule.key = key; 1325 } 1326 1327 if (op == QLatin1String("eq")) 1328 { 1329 rule.op = SearchXml::Equal; 1330 } 1331 else if (op == QLatin1String("ne")) 1332 { 1333 rule.op = SearchXml::Unequal; 1334 } 1335 else if (op == QLatin1String("lt")) 1336 { 1337 rule.op = SearchXml::LessThan; 1338 } 1339 else if (op == QLatin1String("lte")) 1340 { 1341 rule.op = SearchXml::LessThanOrEqual; 1342 } 1343 else if (op == QLatin1String("gt")) 1344 { 1345 rule.op = SearchXml::GreaterThan; 1346 } 1347 else if (op == QLatin1String("gte")) 1348 { 1349 rule.op = SearchXml::GreaterThanOrEqual; 1350 } 1351 else if (op == QLatin1String("like")) 1352 { 1353 if (key == QLatin1String("tag")) 1354 { 1355 rule.op = SearchXml::InTree; 1356 } 1357 else 1358 { 1359 rule.op = SearchXml::Like; 1360 } 1361 } 1362 else if (op == QLatin1String("nlike")) 1363 { 1364 if (key == QLatin1String("tag")) 1365 { 1366 rule.op = SearchXml::NotInTree; 1367 } 1368 else 1369 { 1370 rule.op = SearchXml::NotLike; 1371 } 1372 } 1373 1374 rule.val = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".val")); 1375 1376 rulesMap.insert(i, rule); 1377 } 1378 1379 SearchXmlWriter writer; 1380 1381 // set an attribute marking this search as converted from 0.9 style search 1382 writer.writeAttribute(QLatin1String("convertedFrom09Url"), QLatin1String("true")); 1383 writer.writeGroup(); 1384 1385 QStringList strList = url.path().split(QLatin1Char(' '), QT_SKIP_EMPTY_PARTS); 1386 1387 for (QStringList::const_iterator it = strList.constBegin() ; it != strList.constEnd() ; ++it) 1388 { 1389 bool ok; 1390 int num = (*it).toInt(&ok); 1391 1392 if (ok) 1393 { 1394 RuleTypeForConversion rule = rulesMap[num]; 1395 writer.writeField(rule.key, rule.op); 1396 writer.writeValue(rule.val); 1397 writer.finishField(); 1398 } 1399 else 1400 { 1401 QString expr = (*it).trimmed(); 1402 1403 if (expr == QLatin1String("AND")) 1404 { 1405 // add another field 1406 } 1407 else if (expr == QLatin1String("OR")) 1408 { 1409 // open a new group 1410 writer.finishGroup(); 1411 writer.writeGroup(); 1412 writer.setGroupOperator(SearchXml::Or); 1413 } 1414 else if (expr == QLatin1String("(")) 1415 { 1416 // open a subgroup 1417 writer.writeGroup(); 1418 } 1419 else if (expr == QLatin1String(")")) 1420 { 1421 writer.finishGroup(); 1422 } 1423 } 1424 } 1425 1426 writer.finishGroup(); 1427 writer.finish(); 1428 1429 return writer.xml(); 1430 } 1431 1432 QString ItemQueryBuilder::buildQueryFromUrl(const QUrl& url, QList<QVariant>* boundValues) const 1433 { 1434 int count = QUrlQuery(url).queryItemValue(QLatin1String("count")).toInt(); 1435 1436 if (count <= 0) 1437 { 1438 return QString(); 1439 } 1440 1441 QMap<int, RuleType> rulesMap; 1442 1443 for (int i = 1 ; i <= count ; ++i) 1444 { 1445 RuleType rule; 1446 1447 QString key = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".key")).toLower(); 1448 QString op = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".op")).toLower(); 1449 1450 if (key == QLatin1String("album")) 1451 { 1452 rule.key = ALBUM; 1453 } 1454 else if (key == QLatin1String("albumname")) 1455 { 1456 rule.key = ALBUMNAME; 1457 } 1458 else if (key == QLatin1String("albumcaption")) 1459 { 1460 rule.key = ALBUMCAPTION; 1461 } 1462 else if (key == QLatin1String("albumcollection")) 1463 { 1464 rule.key = ALBUMCOLLECTION; 1465 } 1466 else if (key == QLatin1String("imagename")) 1467 { 1468 rule.key = IMAGENAME; 1469 } 1470 else if (key == QLatin1String("imagecaption")) 1471 { 1472 rule.key = IMAGECAPTION; 1473 } 1474 else if (key == QLatin1String("imagedate")) 1475 { 1476 rule.key = IMAGEDATE; 1477 } 1478 else if (key == QLatin1String("tag")) 1479 { 1480 rule.key = TAG; 1481 } 1482 else if (key == QLatin1String("tagname")) 1483 { 1484 rule.key = TAGNAME; 1485 } 1486 else if (key == QLatin1String("keyword")) 1487 { 1488 rule.key = KEYWORD; 1489 } 1490 else if (key == QLatin1String("rating")) 1491 { 1492 rule.key = RATING; 1493 } 1494 else 1495 { 1496 qCWarning(DIGIKAM_DATABASE_LOG) << "Unknown rule type: " << key << " passed to kioslave"; 1497 continue; 1498 } 1499 1500 if (op == QLatin1String("eq")) 1501 { 1502 rule.op = EQ; 1503 } 1504 else if (op == QLatin1String("ne")) 1505 { 1506 rule.op = NE; 1507 } 1508 else if (op == QLatin1String("lt")) 1509 { 1510 rule.op = LT; 1511 } 1512 else if (op == QLatin1String("lte")) 1513 { 1514 rule.op = LTE; 1515 } 1516 else if (op == QLatin1String("gt")) 1517 { 1518 rule.op = GT; 1519 } 1520 else if (op == QLatin1String("gte")) 1521 { 1522 rule.op = GTE; 1523 } 1524 else if (op == QLatin1String("like")) 1525 { 1526 rule.op = LIKE; 1527 } 1528 else if (op == QLatin1String("nlike")) 1529 { 1530 rule.op = NLIKE; 1531 } 1532 else 1533 { 1534 qCWarning(DIGIKAM_DATABASE_LOG) << "Unknown op type: " << op << " passed to dbjob"; 1535 continue; 1536 } 1537 1538 rule.val = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".val")); 1539 1540 rulesMap.insert(i, rule); 1541 } 1542 1543 QString sqlQuery; 1544 SubQueryBuilder subQuery; 1545 QStringList strList = url.path().split(QLatin1Char(' '), QT_SKIP_EMPTY_PARTS); 1546 1547 for (QStringList::const_iterator it = strList.constBegin() ; it != strList.constEnd() ; ++it) 1548 { 1549 bool ok; 1550 int num = (*it).toInt(&ok); 1551 1552 if (ok) 1553 { 1554 RuleType rule = rulesMap[num]; 1555 1556 if (rule.key == KEYWORD) 1557 { 1558 bool exact; 1559 QString possDate = possibleDate(rule.val, exact); 1560 1561 if (!possDate.isEmpty()) 1562 { 1563 rule.key = IMAGEDATE; 1564 rule.val = possDate; 1565 1566 if (exact) 1567 { 1568 rule.op = EQ; 1569 } 1570 else 1571 { 1572 rule.op = LIKE; 1573 } 1574 1575 sqlQuery += subQuery.build(rule.key, rule.op, rule.val, boundValues); 1576 } 1577 else 1578 { 1579 QList<SKey> todo; 1580 todo.append( ALBUMNAME ); 1581 todo.append( IMAGENAME ); 1582 todo.append( TAGNAME ); 1583 todo.append( ALBUMCAPTION ); 1584 todo.append( ALBUMCOLLECTION ); 1585 todo.append( IMAGECAPTION ); 1586 todo.append( RATING ); 1587 1588 sqlQuery += QLatin1Char('('); 1589 QList<SKey>::const_iterator it2 = todo.constBegin(); 1590 1591 while (it2 != todo.constEnd()) 1592 { 1593 sqlQuery += subQuery.build(*it2, rule.op, rule.val, boundValues); 1594 ++it2; 1595 1596 if (it2 != todo.constEnd()) 1597 { 1598 sqlQuery += QLatin1String(" OR "); 1599 } 1600 } 1601 1602 sqlQuery += QLatin1Char(')'); 1603 } 1604 } 1605 else 1606 { 1607 sqlQuery += subQuery.build(rule.key, rule.op, rule.val, boundValues); 1608 } 1609 } 1610 else 1611 { 1612 sqlQuery += QLatin1Char(' ') + *it + QLatin1Char(' '); 1613 } 1614 } 1615 1616 return sqlQuery; 1617 } 1618 1619 QString ItemQueryBuilder::possibleDate(const QString& str, bool& exact) const 1620 { 1621 QDate date = QDate::fromString(str, Qt::ISODate); 1622 1623 if (date.isValid()) 1624 { 1625 exact = true; 1626 return date.toString(Qt::ISODate); 1627 } 1628 1629 exact = false; 1630 bool ok; 1631 int num = str.toInt(&ok); 1632 1633 if (ok) 1634 { 1635 // ok. its an int, does it look like a year? 1636 if ((1970 <= num) && (num <= QDate::currentDate().year())) 1637 { 1638 // very sure its a year 1639 return QString::fromUtf8("%1-%-%").arg(num); 1640 } 1641 } 1642 else 1643 { 1644 // hmm... not a year. is it a particular month? 1645 for (int i = 1 ; i <= 12 ; ++i) 1646 { 1647 if ((str.toLower() == m_shortMonths[i-1]) || 1648 (str.toLower() == m_longMonths[i-1])) 1649 { 1650 QString monGlob; 1651 monGlob = QString().asprintf("%.2d", i); 1652 monGlob = QString::fromUtf8("%-") + monGlob + QString::fromUtf8("-%"); 1653 1654 return monGlob; 1655 } 1656 } 1657 } 1658 1659 return QString(); 1660 } 1661 1662 } // namespace Digikam