File indexing completed on 2024-04-28 15:17:41

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.type() == QVariant::String) {
0052             d->m_comp = Contains;
0053         } else if (value.type() == QVariant::DateTime) {
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()
0122 {
0123     delete d;
0124 }
0125 
0126 bool Term::isValid() const
0127 {
0128     // Terms with an operator but no subterms are still valid
0129     if (d->m_op != Term::None) {
0130         return true;
0131     }
0132 
0133     if (d->m_comp == Term::Auto) {
0134         return false;
0135     }
0136 
0137     return true;
0138 }
0139 
0140 void Term::setNegation(bool isNegated)
0141 {
0142     d->m_isNegated = isNegated;
0143 }
0144 
0145 bool Term::isNegated() const
0146 {
0147     return d->m_isNegated;
0148 }
0149 
0150 bool Term::negated() const
0151 {
0152     return d->m_isNegated;
0153 }
0154 
0155 void Term::addSubTerm(const Term& term)
0156 {
0157     d->m_subTerms << term;
0158 }
0159 
0160 void Term::setSubTerms(const QList<Term>& terms)
0161 {
0162     d->m_subTerms = terms;
0163 }
0164 
0165 Term Term::subTerm() const
0166 {
0167     if (!d->m_subTerms.isEmpty()) {
0168         return d->m_subTerms.first();
0169     }
0170 
0171     return Term();
0172 }
0173 
0174 QList<Term> Term::subTerms() const
0175 {
0176     return d->m_subTerms;
0177 }
0178 
0179 void Term::setOperation(Term::Operation op)
0180 {
0181     d->m_op = op;
0182 }
0183 
0184 Term::Operation Term::operation() const
0185 {
0186     return d->m_op;
0187 }
0188 
0189 bool Term::empty() const
0190 {
0191     return isEmpty();
0192 }
0193 
0194 bool Term::isEmpty() const
0195 {
0196     return d->m_property.isEmpty() && d->m_value.isNull() && d->m_subTerms.isEmpty();
0197 }
0198 
0199 QString Term::property() const
0200 {
0201     return d->m_property;
0202 }
0203 
0204 void Term::setProperty(const QString& property)
0205 {
0206     d->m_property = property;
0207 }
0208 
0209 void Term::setValue(const QVariant& value)
0210 {
0211     d->m_value = value;
0212 }
0213 
0214 QVariant Term::value() const
0215 {
0216     return d->m_value;
0217 }
0218 
0219 Term::Comparator Term::comparator() const
0220 {
0221     return d->m_comp;
0222 }
0223 
0224 void Term::setComparator(Term::Comparator c)
0225 {
0226     d->m_comp = c;
0227 }
0228 
0229 void Term::setUserData(const QString& name, const QVariant& value)
0230 {
0231     d->m_userData.insert(name, value);
0232 }
0233 
0234 QVariant Term::userData(const QString& name) const
0235 {
0236     return d->m_userData.value(name);
0237 }
0238 
0239 QVariantMap Term::toVariantMap() const
0240 {
0241     QVariantMap map;
0242     if (d->m_op != None) {
0243         QVariantList variantList;
0244         for (const Term& term : std::as_const(d->m_subTerms)) {
0245             variantList << QVariant(term.toVariantMap());
0246         }
0247 
0248         if (d->m_op == And) {
0249             map[QStringLiteral("$and")] = variantList;
0250         } else {
0251             map[QStringLiteral("$or")] = variantList;
0252         }
0253 
0254         return map;
0255     }
0256 
0257     QString op;
0258     switch (d->m_comp) {
0259     case Equal:
0260         map[d->m_property] = d->m_value;
0261         return map;
0262 
0263     case Contains:
0264         op = QStringLiteral("$ct");
0265         break;
0266 
0267     case Greater:
0268         op = QStringLiteral("$gt");
0269         break;
0270 
0271     case GreaterEqual:
0272         op = QStringLiteral("$gte");
0273         break;
0274 
0275     case Less:
0276         op = QStringLiteral("$lt");
0277         break;
0278 
0279     case LessEqual:
0280         op = QStringLiteral("$lte");
0281         break;
0282 
0283     case Auto:
0284         Q_ASSERT(0);
0285     }
0286 
0287     QVariantMap m;
0288     m[op] = d->m_value;
0289     map[d->m_property] = QVariant(m);
0290 
0291     return map;
0292 }
0293 
0294 namespace {
0295     // QJson does not recognize QDate/QDateTime parameters. We try to guess
0296     // and see if they can be converted into date/datetime.
0297     QVariant tryConvert(const QVariant& var) {
0298         if (var.canConvert(QVariant::DateTime)) {
0299             QDateTime dt = var.toDateTime();
0300             if (!dt.isValid()) {
0301                 return var;
0302             }
0303 
0304             if (!var.toString().contains(QLatin1Char('T'))) {
0305                 return QVariant(var.toDate());
0306             }
0307             return dt;
0308         }
0309         return var;
0310     }
0311 }
0312 
0313 Term Term::fromVariantMap(const QVariantMap& map)
0314 {
0315     if (map.size() != 1) {
0316         return Term();
0317     }
0318 
0319     Term term;
0320 
0321     QString andOrString;
0322     if (map.contains(QLatin1String("$and"))) {
0323         andOrString = QStringLiteral("$and");
0324         term.setOperation(And);
0325     }
0326     else if (map.contains(QLatin1String("$or"))) {
0327         andOrString = QStringLiteral("$or");
0328         term.setOperation(Or);
0329     }
0330 
0331     if (!andOrString.isEmpty()) {
0332         QList<Term> subTerms;
0333 
0334         const QVariantList list = map[andOrString].toList();
0335         for (const QVariant &var : list) {
0336             subTerms << Term::fromVariantMap(var.toMap());
0337         }
0338 
0339         term.setSubTerms(subTerms);
0340         return term;
0341     }
0342 
0343     QString prop = map.cbegin().key();
0344     term.setProperty(prop);
0345 
0346     QVariant value = map.value(prop);
0347     if (value.type() == QVariant::Map) {
0348         QVariantMap mapVal = value.toMap();
0349         if (mapVal.size() != 1) {
0350             return term;
0351         }
0352 
0353         QString op = mapVal.cbegin().key();
0354         Term::Comparator com;
0355         if (op == QLatin1String("$ct")) {
0356             com = Contains;
0357         } else if (op == QLatin1String("$gt")) {
0358             com = Greater;
0359         } else if (op == QLatin1String("$gte")) {
0360             com = GreaterEqual;
0361         } else if (op == QLatin1String("$lt")) {
0362             com = Less;
0363         } else if (op == QLatin1String("$lte")) {
0364             com = LessEqual;
0365         } else {
0366             return term;
0367         }
0368 
0369         term.setComparator(com);
0370         term.setValue(tryConvert(mapVal.value(op)));
0371 
0372         return term;
0373     }
0374 
0375     term.setComparator(Equal);
0376     term.setValue(tryConvert(value));
0377 
0378     return term;
0379 }
0380 
0381 bool Term::operator==(const Term& rhs) const
0382 {
0383     if (d->m_op != rhs.d->m_op || d->m_comp != rhs.d->m_comp ||
0384         d->m_isNegated != rhs.d->m_isNegated || d->m_property != rhs.d->m_property ||
0385         d->m_value != rhs.d->m_value)
0386     {
0387         return false;
0388     }
0389 
0390     if (d->m_subTerms.size() != rhs.d->m_subTerms.size()) {
0391         return false;
0392     }
0393 
0394     if (d->m_subTerms.isEmpty()) {
0395         return true;
0396     }
0397 
0398     for (const Term& t : std::as_const(d->m_subTerms)) {
0399         if (!rhs.d->m_subTerms.contains(t)) {
0400             return false;
0401         }
0402     }
0403 
0404     return true;
0405 }
0406 
0407 Term& Term::operator=(const Term& rhs)
0408 {
0409     *d = *rhs.d;
0410     return *this;
0411 }
0412 
0413 char *toString(const Term& term)
0414 {
0415     QString buffer;
0416     QDebug stream(&buffer);
0417     stream << term;
0418     return qstrdup(buffer.toUtf8().constData());
0419 }
0420 
0421 } // namespace Baloo
0422 
0423 namespace {
0424     QString comparatorToString(Baloo::Term::Comparator c) {
0425         switch (c) {
0426         case Baloo::Term::Auto:
0427             return QStringLiteral("Auto");
0428         case Baloo::Term::Equal:
0429             return QStringLiteral("=");
0430         case Baloo::Term::Contains:
0431             return QStringLiteral(":");
0432         case Baloo::Term::Less:
0433             return QStringLiteral("<");
0434         case Baloo::Term::LessEqual:
0435             return QStringLiteral("<=");
0436         case Baloo::Term::Greater:
0437             return QStringLiteral(">");
0438         case Baloo::Term::GreaterEqual:
0439             return QStringLiteral(">=");
0440         }
0441 
0442         return QString();
0443     }
0444 
0445     QString operationToString(Baloo::Term::Operation op) {
0446         switch (op) {
0447         case Baloo::Term::None:
0448             return QStringLiteral("NONE");
0449         case Baloo::Term::And:
0450             return QStringLiteral("AND");
0451         case Baloo::Term::Or:
0452             return QStringLiteral("OR");
0453         }
0454 
0455         return QString();
0456     }
0457 }
0458 
0459 QDebug operator <<(QDebug d, const Baloo::Term& t)
0460 {
0461     QDebugStateSaver saver(d);
0462     d.noquote().nospace();
0463     if (t.subTerms().isEmpty()) {
0464         if (!t.property().isEmpty()) {
0465             d << t.property();
0466         }
0467     d << comparatorToString(t.comparator());
0468         if (t.value().type() == QVariant::String) {
0469             d << QLatin1Char('"') << t.value().toString() << QLatin1Char('"');
0470         } else {
0471             d << t.value().typeName() << QLatin1Char('(')
0472               << t.value().toString() << QLatin1Char(')');
0473         }
0474     }
0475     else {
0476         d << "[" << operationToString(t.operation());
0477         const auto subTerms = t.subTerms();
0478         for (const Baloo::Term& term : subTerms) {
0479             d << QLatin1Char(' ') << term;
0480         }
0481         d << "]";
0482     }
0483     return d;
0484 }