Akonadi Search

term.cpp
1/*
2 * This file is part of the KDE Akonadi Search Project
3 * SPDX-FileCopyrightText: 2013 Vishesh Handa <me@vhanda.in>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 *
7 */
8
9#include "term.h"
10using namespace Qt::Literals::StringLiterals;
11
12#include <QDateTime>
13
14using namespace Akonadi::Search;
15
16class Akonadi::Search::TermPrivate
17{
18public:
19 Term::Operation m_op = Term::None;
20 Term::Comparator m_comp = Term::Auto;
21
22 QString m_property;
23 QVariant m_value;
24
25 bool m_isNegated = false;
26
27 QList<Term> m_subTerms;
28 QVariantHash m_userData;
29};
30
31Term::Term()
32 : d(new TermPrivate)
33{
34}
35
36Term::Term(const Term &t)
37 : d(new TermPrivate(*t.d))
38{
39}
40
41Term::Term(const QString &property)
42 : d(new TermPrivate)
43{
44 d->m_property = property;
45}
46
47Term::Term(const QString &property, const QVariant &value, Term::Comparator c)
48 : d(new TermPrivate)
49{
50 d->m_property = property;
51 d->m_value = value;
52
53 if (c == Auto) {
54 auto valueType = value.metaType().id();
55 if (valueType == QMetaType::QString) {
56 d->m_comp = Contains;
57 } else if (valueType == QMetaType::QDateTime) {
58 d->m_comp = Contains;
59 } else {
60 d->m_comp = Equal;
61 }
62 } else {
63 d->m_comp = c;
64 }
65}
66
67/*
68Term::Term(const QString& property, const QVariant& start, const QVariant& end)
69 : d(new TermPrivate)
70{
71 d->m_property = property;
72 d->m_op = Range;
73
74 // FIXME: How to save range queries?
75}
76*/
77
78Term::Term(Term::Operation op)
79 : d(new TermPrivate)
80{
81 d->m_op = op;
82}
83
84Term::Term(Term::Operation op, const Term &t)
85 : d(new TermPrivate)
86{
87 d->m_op = op;
88 d->m_subTerms << t;
89}
90
91Term::Term(Term::Operation op, const QList<Term> &t)
92 : d(new TermPrivate)
93{
94 d->m_op = op;
95 d->m_subTerms = t;
96}
97
98Term::Term(const Term &lhs, Term::Operation op, const Term &rhs)
99 : d(new TermPrivate)
100{
101 d->m_op = op;
102 d->m_subTerms << lhs;
103 d->m_subTerms << rhs;
104}
105
106Term::~Term() = default;
107
108bool Term::isValid() const
109{
110 if (d->m_property.isEmpty()) {
111 if (d->m_op == Term::None) {
112 return false;
113 }
114
115 return d->m_property.isEmpty() && d->m_value.isNull();
116 }
117
118 return true;
119}
120
121void Term::setNegation(bool isNegated)
122{
123 d->m_isNegated = isNegated;
124}
125
126bool Term::isNegated() const
127{
128 return d->m_isNegated;
129}
130
131bool Term::negated() const
132{
133 return d->m_isNegated;
134}
135
136void Term::addSubTerm(const Term &term)
137{
138 d->m_subTerms << term;
139}
140
141void Term::setSubTerms(const QList<Term> &terms)
142{
143 d->m_subTerms = terms;
144}
145
147{
148 if (!d->m_subTerms.isEmpty()) {
149 return d->m_subTerms.first();
150 }
151
152 return {};
153}
154
155QList<Term> Term::subTerms() const
156{
157 return d->m_subTerms;
158}
159
160void Term::setOperation(Term::Operation op)
161{
162 d->m_op = op;
163}
164
165Term::Operation Term::operation() const
166{
167 return d->m_op;
168}
169
170bool Term::empty() const
171{
172 return isEmpty();
173}
174
175bool Term::isEmpty() const
176{
177 return d->m_property.isEmpty() && d->m_value.isNull() && d->m_subTerms.isEmpty();
178}
179
181{
182 return d->m_property;
183}
184
185void Term::setProperty(const QString &property)
186{
187 d->m_property = property;
188}
189
190void Term::setValue(const QVariant &value)
191{
192 d->m_value = value;
193}
194
195QVariant Term::value() const
196{
197 return d->m_value;
198}
199
200Term::Comparator Term::comparator() const
201{
202 return d->m_comp;
203}
204
205void Term::setComparator(Term::Comparator c)
206{
207 d->m_comp = c;
208}
209
210void Term::setUserData(const QString &name, const QVariant &value)
211{
212 d->m_userData.insert(name, value);
213}
214
215QVariant Term::userData(const QString &name) const
216{
217 return d->m_userData.value(name);
218}
219
220QVariantMap Term::toVariantMap() const
221{
222 QVariantMap map;
223 if (d->m_op != None) {
224 QVariantList variantList;
225 variantList.reserve(d->m_subTerms.count());
226 for (const Term &term : std::as_const(d->m_subTerms)) {
227 variantList << QVariant(term.toVariantMap());
228 }
229
230 if (d->m_op == And) {
231 map[QStringLiteral("$and")] = variantList;
232 } else {
233 map[QStringLiteral("$or")] = variantList;
234 }
235
236 return map;
237 }
238
239 QString op;
240 switch (d->m_comp) {
241 case Equal:
242 map[d->m_property] = d->m_value;
243 return map;
244
245 case Contains:
246 op = QStringLiteral("$ct");
247 break;
248
249 case Greater:
250 op = QStringLiteral("$gt");
251 break;
252
253 case GreaterEqual:
254 op = QStringLiteral("$gte");
255 break;
256
257 case Less:
258 op = QStringLiteral("$lt");
259 break;
260
261 case LessEqual:
262 op = QStringLiteral("$lte");
263 break;
264
265 default:
266 return {};
267 }
268
269 QVariantMap m;
270 m[op] = d->m_value;
271 map[d->m_property] = QVariant(m);
272
273 return map;
274}
275
276namespace
277{
278// QJson does not recognize QDate/QDateTime parameters. We try to guess
279// and see if they can be converted into date/datetime.
280QVariant tryConvert(const QVariant &var)
281{
282 if (var.canConvert<QDateTime>()) {
283 QDateTime dt = var.toDateTime();
284 if (!dt.isValid()) {
285 return var;
286 }
287
288 if (!var.toString().contains(QLatin1Char('T'))) {
289 return QVariant(var.toDate());
290 }
291 return dt;
292 }
293 return var;
294}
295}
296
297Term Term::fromVariantMap(const QVariantMap &map)
298{
299 if (map.size() != 1) {
300 return {};
301 }
302
303 Term term;
304
305 QString andOrString;
306 if (map.contains(QStringLiteral("$and"))) {
307 andOrString = QStringLiteral("$and");
308 term.setOperation(And);
309 } else if (map.contains(QStringLiteral("$or"))) {
310 andOrString = QStringLiteral("$or");
311 term.setOperation(Or);
312 }
313
314 if (andOrString.size()) {
315 QList<Term> subTerms;
316
317 const QVariantList list = map[andOrString].toList();
318 subTerms.reserve(list.count());
319 for (const QVariant &var : list) {
320 subTerms << Term::fromVariantMap(var.toMap());
321 }
322
323 term.setSubTerms(subTerms);
324 return term;
325 }
326
327 QString prop = map.cbegin().key();
328 term.setProperty(prop);
329
330 QVariant value = map.value(prop);
331 if (value.userType() == QMetaType::QVariantMap) {
332 QVariantMap map = value.toMap();
333 if (map.size() != 1) {
334 return term;
335 }
336
337 QString op = map.cbegin().key();
338 Term::Comparator com;
339 if (op == "$ct"_L1) {
340 com = Contains;
341 } else if (op == "$gt"_L1) {
342 com = Greater;
343 } else if (op == "$gte"_L1) {
344 com = GreaterEqual;
345 } else if (op == "$lt"_L1) {
346 com = Less;
347 } else if (op == "$lte"_L1) {
348 com = LessEqual;
349 } else {
350 return term;
351 }
352
353 term.setComparator(com);
354 term.setValue(tryConvert(map.value(op)));
355
356 return term;
357 }
358
359 term.setComparator(Equal);
360 term.setValue(tryConvert(value));
361
362 return term;
363}
364
365bool Term::operator==(const Term &rhs) const
366{
367 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
368 || d->m_value != rhs.d->m_value) {
369 return false;
370 }
371
372 if (d->m_subTerms.size() != rhs.d->m_subTerms.size()) {
373 return false;
374 }
375
376 if (d->m_subTerms.isEmpty()) {
377 return true;
378 }
379
380 for (const Term &t : std::as_const(d->m_subTerms)) {
381 if (!rhs.d->m_subTerms.contains(t)) {
382 return false;
383 }
384 }
385
386 return true;
387}
388
389Term &Term::operator=(const Term &rhs)
390{
391 *d = *rhs.d;
392 return *this;
393}
394
395namespace
396{
397QString comparatorToString(Term::Comparator c)
398{
399 switch (c) {
400 case Term::Auto:
401 return QStringLiteral("Auto");
402 case Term::Equal:
403 return QStringLiteral("=");
404 case Term::Contains:
405 return QStringLiteral(":");
406 case Term::Less:
407 return QStringLiteral("<");
408 case Term::LessEqual:
409 return QStringLiteral("<=");
410 case Term::Greater:
411 return QStringLiteral(">");
412 case Term::GreaterEqual:
413 return QStringLiteral(">=");
414 }
415
416 return {};
417}
418
419QString operationToString(Term::Operation op)
420{
421 switch (op) {
422 case Term::None:
423 return QStringLiteral("NONE");
424 case Term::And:
425 return QStringLiteral("AND");
426 case Term::Or:
427 return QStringLiteral("OR");
428 }
429
430 return {};
431}
432} // namespace
433
434QDebug operator<<(QDebug d, const Term &t)
435{
436 if (t.subTerms().isEmpty()) {
437 d << QStringLiteral("(%1 %2 %3 (%4))")
438 .arg(t.property(), comparatorToString(t.comparator()), t.value().toString(), QString::fromLatin1(t.value().typeName()))
439 .toUtf8()
440 .constData();
441 } else {
442 d << "(" << operationToString(t.operation()).toUtf8().constData();
443 const QList<Term> subterms = t.subTerms();
444 for (const Term &term : std::as_const(subterms)) {
445 d << term;
446 }
447 d << ")";
448 }
449 return d;
450}
Search term.
Definition term.h:27
void setNegation(bool isNegated)
Negate this term.
Definition term.cpp:121
QString property() const
Return the property this term is targeting.
Definition term.cpp:180
Term subTerm() const
Returns the first subTerm in the list of subTerms.
Definition term.cpp:146
Akonadi search infrastructure.
Definition core/query.h:21
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
bool isValid() const const
qsizetype count() const const
void reserve(qsizetype size)
int id() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
qsizetype size() const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
bool canConvert() const const
QMetaType metaType() const const
QDate toDate() const const
QDateTime toDateTime() const const
QMap< QString, QVariant > toMap() const const
QString toString() const const
const char * typeName() const const
int userType() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:58:58 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.