File indexing completed on 2024-04-28 03:51:52

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