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

source code of baloo/src/lib/term.cpp