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 }