Akonadi Search

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

KDE's Doxygen guidelines are available online.