File indexing completed on 2024-05-19 05:11:51

0001 /*
0002  * This file is part of the KDE Akonadi Search Project
0003  * SPDX-FileCopyrightText: 2013 Vishesh Handa <me@vhanda.in>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  *
0007  */
0008 
0009 #include "term.h"
0010 
0011 #include <QDateTime>
0012 
0013 using namespace Akonadi::Search;
0014 
0015 class Akonadi::Search::TermPrivate
0016 {
0017 public:
0018     Term::Operation m_op = Term::None;
0019     Term::Comparator m_comp = Term::Auto;
0020 
0021     QString m_property;
0022     QVariant m_value;
0023 
0024     bool m_isNegated = false;
0025 
0026     QList<Term> m_subTerms;
0027     QVariantHash m_userData;
0028 };
0029 
0030 Term::Term()
0031     : d(new TermPrivate)
0032 {
0033 }
0034 
0035 Term::Term(const Term &t)
0036     : d(new TermPrivate(*t.d))
0037 {
0038 }
0039 
0040 Term::Term(const QString &property)
0041     : d(new TermPrivate)
0042 {
0043     d->m_property = property;
0044 }
0045 
0046 Term::Term(const QString &property, const QVariant &value, Term::Comparator c)
0047     : d(new TermPrivate)
0048 {
0049     d->m_property = property;
0050     d->m_value = value;
0051 
0052     if (c == Auto) {
0053         auto valueType = value.metaType().id();
0054         if (valueType == QMetaType::QString) {
0055             d->m_comp = Contains;
0056         } else if (valueType == QMetaType::QDateTime) {
0057             d->m_comp = Contains;
0058         } else {
0059             d->m_comp = Equal;
0060         }
0061     } else {
0062         d->m_comp = c;
0063     }
0064 }
0065 
0066 /*
0067 Term::Term(const QString& property, const QVariant& start, const QVariant& end)
0068     : d(new TermPrivate)
0069 {
0070     d->m_property = property;
0071     d->m_op = Range;
0072 
0073     // FIXME: How to save range queries?
0074 }
0075 */
0076 
0077 Term::Term(Term::Operation op)
0078     : d(new TermPrivate)
0079 {
0080     d->m_op = op;
0081 }
0082 
0083 Term::Term(Term::Operation op, const Term &t)
0084     : d(new TermPrivate)
0085 {
0086     d->m_op = op;
0087     d->m_subTerms << t;
0088 }
0089 
0090 Term::Term(Term::Operation op, const QList<Term> &t)
0091     : d(new TermPrivate)
0092 {
0093     d->m_op = op;
0094     d->m_subTerms = t;
0095 }
0096 
0097 Term::Term(const Term &lhs, Term::Operation op, const Term &rhs)
0098     : d(new TermPrivate)
0099 {
0100     d->m_op = op;
0101     d->m_subTerms << lhs;
0102     d->m_subTerms << rhs;
0103 }
0104 
0105 Term::~Term() = default;
0106 
0107 bool Term::isValid() const
0108 {
0109     if (d->m_property.isEmpty()) {
0110         if (d->m_op == Term::None) {
0111             return false;
0112         }
0113 
0114         return d->m_property.isEmpty() && d->m_value.isNull();
0115     }
0116 
0117     return true;
0118 }
0119 
0120 void Term::setNegation(bool isNegated)
0121 {
0122     d->m_isNegated = isNegated;
0123 }
0124 
0125 bool Term::isNegated() const
0126 {
0127     return d->m_isNegated;
0128 }
0129 
0130 bool Term::negated() const
0131 {
0132     return d->m_isNegated;
0133 }
0134 
0135 void Term::addSubTerm(const Term &term)
0136 {
0137     d->m_subTerms << term;
0138 }
0139 
0140 void Term::setSubTerms(const QList<Term> &terms)
0141 {
0142     d->m_subTerms = terms;
0143 }
0144 
0145 Term Term::subTerm() const
0146 {
0147     if (!d->m_subTerms.isEmpty()) {
0148         return d->m_subTerms.first();
0149     }
0150 
0151     return {};
0152 }
0153 
0154 QList<Term> Term::subTerms() const
0155 {
0156     return d->m_subTerms;
0157 }
0158 
0159 void Term::setOperation(Term::Operation op)
0160 {
0161     d->m_op = op;
0162 }
0163 
0164 Term::Operation Term::operation() const
0165 {
0166     return d->m_op;
0167 }
0168 
0169 bool Term::empty() const
0170 {
0171     return isEmpty();
0172 }
0173 
0174 bool Term::isEmpty() const
0175 {
0176     return d->m_property.isEmpty() && d->m_value.isNull() && d->m_subTerms.isEmpty();
0177 }
0178 
0179 QString Term::property() const
0180 {
0181     return d->m_property;
0182 }
0183 
0184 void Term::setProperty(const QString &property)
0185 {
0186     d->m_property = property;
0187 }
0188 
0189 void Term::setValue(const QVariant &value)
0190 {
0191     d->m_value = value;
0192 }
0193 
0194 QVariant Term::value() const
0195 {
0196     return d->m_value;
0197 }
0198 
0199 Term::Comparator Term::comparator() const
0200 {
0201     return d->m_comp;
0202 }
0203 
0204 void Term::setComparator(Term::Comparator c)
0205 {
0206     d->m_comp = c;
0207 }
0208 
0209 void Term::setUserData(const QString &name, const QVariant &value)
0210 {
0211     d->m_userData.insert(name, value);
0212 }
0213 
0214 QVariant Term::userData(const QString &name) const
0215 {
0216     return d->m_userData.value(name);
0217 }
0218 
0219 QVariantMap Term::toVariantMap() const
0220 {
0221     QVariantMap map;
0222     if (d->m_op != None) {
0223         QVariantList variantList;
0224         variantList.reserve(d->m_subTerms.count());
0225         for (const Term &term : std::as_const(d->m_subTerms)) {
0226             variantList << QVariant(term.toVariantMap());
0227         }
0228 
0229         if (d->m_op == And) {
0230             map[QStringLiteral("$and")] = variantList;
0231         } else {
0232             map[QStringLiteral("$or")] = variantList;
0233         }
0234 
0235         return map;
0236     }
0237 
0238     QString op;
0239     switch (d->m_comp) {
0240     case Equal:
0241         map[d->m_property] = d->m_value;
0242         return map;
0243 
0244     case Contains:
0245         op = QStringLiteral("$ct");
0246         break;
0247 
0248     case Greater:
0249         op = QStringLiteral("$gt");
0250         break;
0251 
0252     case GreaterEqual:
0253         op = QStringLiteral("$gte");
0254         break;
0255 
0256     case Less:
0257         op = QStringLiteral("$lt");
0258         break;
0259 
0260     case LessEqual:
0261         op = QStringLiteral("$lte");
0262         break;
0263 
0264     default:
0265         return {};
0266     }
0267 
0268     QVariantMap m;
0269     m[op] = d->m_value;
0270     map[d->m_property] = QVariant(m);
0271 
0272     return map;
0273 }
0274 
0275 namespace
0276 {
0277 // QJson does not recognize QDate/QDateTime parameters. We try to guess
0278 // and see if they can be converted into date/datetime.
0279 QVariant tryConvert(const QVariant &var)
0280 {
0281     if (var.canConvert<QDateTime>()) {
0282         QDateTime dt = var.toDateTime();
0283         if (!dt.isValid()) {
0284             return var;
0285         }
0286 
0287         if (!var.toString().contains(QLatin1Char('T'))) {
0288             return QVariant(var.toDate());
0289         }
0290         return dt;
0291     }
0292     return var;
0293 }
0294 }
0295 
0296 Term Term::fromVariantMap(const QVariantMap &map)
0297 {
0298     if (map.size() != 1) {
0299         return {};
0300     }
0301 
0302     Term term;
0303 
0304     QString andOrString;
0305     if (map.contains(QStringLiteral("$and"))) {
0306         andOrString = QStringLiteral("$and");
0307         term.setOperation(And);
0308     } else if (map.contains(QStringLiteral("$or"))) {
0309         andOrString = QStringLiteral("$or");
0310         term.setOperation(Or);
0311     }
0312 
0313     if (andOrString.size()) {
0314         QList<Term> subTerms;
0315 
0316         const QVariantList list = map[andOrString].toList();
0317         subTerms.reserve(list.count());
0318         for (const QVariant &var : list) {
0319             subTerms << Term::fromVariantMap(var.toMap());
0320         }
0321 
0322         term.setSubTerms(subTerms);
0323         return term;
0324     }
0325 
0326     QString prop = map.cbegin().key();
0327     term.setProperty(prop);
0328 
0329     QVariant value = map.value(prop);
0330     if (value.userType() == QMetaType::QVariantMap) {
0331         QVariantMap map = value.toMap();
0332         if (map.size() != 1) {
0333             return term;
0334         }
0335 
0336         QString op = map.cbegin().key();
0337         Term::Comparator com;
0338         if (op == QLatin1StringView("$ct")) {
0339             com = Contains;
0340         } else if (op == QLatin1StringView("$gt")) {
0341             com = Greater;
0342         } else if (op == QLatin1StringView("$gte")) {
0343             com = GreaterEqual;
0344         } else if (op == QLatin1StringView("$lt")) {
0345             com = Less;
0346         } else if (op == QLatin1StringView("$lte")) {
0347             com = LessEqual;
0348         } else {
0349             return term;
0350         }
0351 
0352         term.setComparator(com);
0353         term.setValue(tryConvert(map.value(op)));
0354 
0355         return term;
0356     }
0357 
0358     term.setComparator(Equal);
0359     term.setValue(tryConvert(value));
0360 
0361     return term;
0362 }
0363 
0364 bool Term::operator==(const Term &rhs) const
0365 {
0366     if (d->m_op != rhs.d->m_op || d->m_comp != rhs.d->m_comp || d->m_isNegated != rhs.d->m_isNegated || d->m_property != rhs.d->m_property
0367         || d->m_value != rhs.d->m_value) {
0368         return false;
0369     }
0370 
0371     if (d->m_subTerms.size() != rhs.d->m_subTerms.size()) {
0372         return false;
0373     }
0374 
0375     if (d->m_subTerms.isEmpty()) {
0376         return true;
0377     }
0378 
0379     for (const Term &t : std::as_const(d->m_subTerms)) {
0380         if (!rhs.d->m_subTerms.contains(t)) {
0381             return false;
0382         }
0383     }
0384 
0385     return true;
0386 }
0387 
0388 Term &Term::operator=(const Term &rhs)
0389 {
0390     *d = *rhs.d;
0391     return *this;
0392 }
0393 
0394 namespace
0395 {
0396 QString comparatorToString(Term::Comparator c)
0397 {
0398     switch (c) {
0399     case Term::Auto:
0400         return QStringLiteral("Auto");
0401     case Term::Equal:
0402         return QStringLiteral("=");
0403     case Term::Contains:
0404         return QStringLiteral(":");
0405     case Term::Less:
0406         return QStringLiteral("<");
0407     case Term::LessEqual:
0408         return QStringLiteral("<=");
0409     case Term::Greater:
0410         return QStringLiteral(">");
0411     case Term::GreaterEqual:
0412         return QStringLiteral(">=");
0413     }
0414 
0415     return {};
0416 }
0417 
0418 QString operationToString(Term::Operation op)
0419 {
0420     switch (op) {
0421     case Term::None:
0422         return QStringLiteral("NONE");
0423     case Term::And:
0424         return QStringLiteral("AND");
0425     case Term::Or:
0426         return QStringLiteral("OR");
0427     }
0428 
0429     return {};
0430 }
0431 } // namespace
0432 
0433 QDebug operator<<(QDebug d, const Term &t)
0434 {
0435     if (t.subTerms().isEmpty()) {
0436         d << QStringLiteral("(%1 %2 %3 (%4))")
0437                  .arg(t.property(), comparatorToString(t.comparator()), t.value().toString(), QString::fromLatin1(t.value().typeName()))
0438                  .toUtf8()
0439                  .constData();
0440     } else {
0441         d << "(" << operationToString(t.operation()).toUtf8().constData();
0442         const QList<Term> subterms = t.subTerms();
0443         for (const Term &term : std::as_const(subterms)) {
0444             d << term;
0445         }
0446         d << ")";
0447     }
0448     return d;
0449 }