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