Akonadi

querybuilder.cpp
1 /*
2  SPDX-FileCopyrightText: 2007-2012 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "querybuilder.h"
8 #include "akonadiserver_debug.h"
9 #include "dbexception.h"
10 
11 #ifndef QUERYBUILDER_UNITTEST
12 #include "storage/datastore.h"
13 #include "storage/querycache.h"
14 #include "storage/storagedebugger.h"
15 #endif
16 
17 #include <shared/akranges.h>
18 
19 #include <QElapsedTimer>
20 #include <QSqlError>
21 #include <QSqlRecord>
22 
23 using namespace Akonadi::Server;
24 
25 static QLatin1String compareOperatorToString(Query::CompareOperator op)
26 {
27  switch (op) {
28  case Query::Equals:
29  return QLatin1String(" = ");
30  case Query::NotEquals:
31  return QLatin1String(" <> ");
32  case Query::Is:
33  return QLatin1String(" IS ");
34  case Query::IsNot:
35  return QLatin1String(" IS NOT ");
36  case Query::Less:
37  return QLatin1String(" < ");
38  case Query::LessOrEqual:
39  return QLatin1String(" <= ");
40  case Query::Greater:
41  return QLatin1String(" > ");
42  case Query::GreaterOrEqual:
43  return QLatin1String(" >= ");
44  case Query::In:
45  return QLatin1String(" IN ");
46  case Query::NotIn:
47  return QLatin1String(" NOT IN ");
48  case Query::Like:
49  return QLatin1String(" LIKE ");
50  }
51  Q_ASSERT_X(false, "QueryBuilder::compareOperatorToString()", "Unknown compare operator.");
52  return QLatin1String("");
53 }
54 
55 static QLatin1String logicOperatorToString(Query::LogicOperator op)
56 {
57  switch (op) {
58  case Query::And:
59  return QLatin1String(" AND ");
60  case Query::Or:
61  return QLatin1String(" OR ");
62  }
63  Q_ASSERT_X(false, "QueryBuilder::logicOperatorToString()", "Unknown logic operator.");
64  return QLatin1String("");
65 }
66 
67 static QLatin1String sortOrderToString(Query::SortOrder order)
68 {
69  switch (order) {
70  case Query::Ascending:
71  return QLatin1String(" ASC");
72  case Query::Descending:
73  return QLatin1String(" DESC");
74  }
75  Q_ASSERT_X(false, "QueryBuilder::sortOrderToString()", "Unknown sort order.");
76  return QLatin1String("");
77 }
78 
79 static void appendJoined(QString *statement, const QStringList &strings, QLatin1String glue = QLatin1String(", "))
80 {
81  for (int i = 0, c = strings.size(); i < c; ++i) {
82  *statement += strings.at(i);
83  if (i + 1 < c) {
84  *statement += glue;
85  }
86  }
87 }
88 
89 QueryBuilder::QueryBuilder(const QString &table, QueryBuilder::QueryType type)
90  : mTable(table)
91 #ifndef QUERYBUILDER_UNITTEST
92  , mDatabaseType(DbType::type(DataStore::self()->database()))
93  , mQuery(DataStore::self()->database())
94 #else
95  , mDatabaseType(DbType::Unknown)
96 #endif
97  , mType(type)
98  , mIdentificationColumn()
99  , mLimit(-1)
100  , mDistinct(false)
101 {
102  static const QString defaultIdColumn = QStringLiteral("id");
103  mIdentificationColumn = defaultIdColumn;
104 }
105 
107 {
108  mDatabaseType = type;
109 }
110 
111 void QueryBuilder::addJoin(JoinType joinType, const QString &table, const Query::Condition &condition)
112 {
113  Q_ASSERT((joinType == InnerJoin && (mType == Select || mType == Update)) || (joinType == LeftJoin && mType == Select));
114 
115  if (mJoinedTables.contains(table)) {
116  // InnerJoin is more restrictive than a LeftJoin, hence use that in doubt
117  mJoins[table].first = qMin(joinType, mJoins.value(table).first);
118  mJoins[table].second.addCondition(condition);
119  } else {
120  mJoins[table] = qMakePair(joinType, condition);
121  mJoinedTables << table;
122  }
123 }
124 
125 void QueryBuilder::addJoin(JoinType joinType, const QString &table, const QString &col1, const QString &col2)
126 {
127  Query::Condition condition;
128  condition.addColumnCondition(col1, Query::Equals, col2);
129  addJoin(joinType, table, condition);
130 }
131 
133 {
134  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
135  mRootCondition[type].addValueCondition(column, op, value);
136 }
137 
139 {
140  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
141  mRootCondition[type].addColumnCondition(column, op, column2);
142 }
143 
145 {
146  return mQuery;
147 }
148 
149 void QueryBuilder::sqliteAdaptUpdateJoin(Query::Condition &condition)
150 {
151  // FIXME: This does not cover all cases by far. It however can handle most
152  // (probably all) of the update-join queries we do in Akonadi and convert them
153  // properly into a SQLite-compatible query. Better than nothing ;-)
154 
155  if (!condition.mSubConditions.isEmpty()) {
156  for (int i = condition.mSubConditions.count() - 1; i >= 0; --i) {
157  sqliteAdaptUpdateJoin(condition.mSubConditions[i]);
158  }
159  return;
160  }
161 
162  QString table;
163  if (condition.mColumn.contains(QLatin1Char('.'))) {
164  table = condition.mColumn.left(condition.mColumn.indexOf(QLatin1Char('.')));
165  } else {
166  return;
167  }
168 
169  if (!mJoinedTables.contains(table)) {
170  return;
171  }
172 
173  const auto &[type, joinCondition] = mJoins.value(table);
174 
175  QueryBuilder qb(table, Select);
176  qb.addColumn(condition.mColumn);
177  qb.addCondition(joinCondition);
178 
179  // Convert the subquery to string
180  condition.mColumn.reserve(1024);
181  condition.mColumn.resize(0);
182  condition.mColumn += QLatin1String("( ");
183  qb.buildQuery(&condition.mColumn);
184  condition.mColumn += QLatin1String(" )");
185 }
186 
187 void QueryBuilder::buildQuery(QString *statement)
188 {
189  // we add the ON conditions of Inner Joins in a Update query here
190  // but don't want to change the mRootCondition on each exec().
191  Query::Condition whereCondition = mRootCondition[WhereCondition];
192 
193  switch (mType) {
194  case Select:
195  // Enable forward-only on all SELECT queries, since we never need to
196  // iterate backwards. This is a memory optimization.
197  mQuery.setForwardOnly(true);
198  *statement += QLatin1String("SELECT ");
199  if (mDistinct) {
200  *statement += QLatin1String("DISTINCT ");
201  }
202  Q_ASSERT_X(mColumns.count() > 0, "QueryBuilder::exec()", "No columns specified");
203  appendJoined(statement, mColumns);
204  *statement += QLatin1String(" FROM ");
205  *statement += mTable;
206  for (const QString &joinedTable : qAsConst(mJoinedTables)) {
207  const auto &[joinType, joinCond] = mJoins.value(joinedTable);
208  switch (joinType) {
209  case LeftJoin:
210  *statement += QLatin1String(" LEFT JOIN ");
211  break;
212  case InnerJoin:
213  *statement += QLatin1String(" INNER JOIN ");
214  break;
215  }
216  *statement += joinedTable;
217  *statement += QLatin1String(" ON ");
218  buildWhereCondition(statement, joinCond);
219  }
220  break;
221  case Insert: {
222  *statement += QLatin1String("INSERT INTO ");
223  *statement += mTable;
224  *statement += QLatin1String(" (");
225  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
226  *statement += mColumnValues.at(i).first;
227  if (i + 1 < c) {
228  *statement += QLatin1String(", ");
229  }
230  }
231  *statement += QLatin1String(") VALUES (");
232  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
233  bindValue(statement, mColumnValues.at(i).second);
234  if (i + 1 < c) {
235  *statement += QLatin1String(", ");
236  }
237  }
238  *statement += QLatin1Char(')');
239  if (mDatabaseType == DbType::PostgreSQL && !mIdentificationColumn.isEmpty()) {
240  *statement += QLatin1String(" RETURNING ") + mIdentificationColumn;
241  }
242  break;
243  }
244  case Update: {
245  // put the ON condition into the WHERE part of the UPDATE query
246  if (mDatabaseType != DbType::Sqlite) {
247  for (const QString &table : qAsConst(mJoinedTables)) {
248  const auto &[joinType, joinCond] = mJoins.value(table);
249  Q_ASSERT(joinType == InnerJoin);
250  whereCondition.addCondition(joinCond);
251  }
252  } else {
253  // Note: this will modify the whereCondition
254  sqliteAdaptUpdateJoin(whereCondition);
255  }
256 
257  *statement += QLatin1String("UPDATE ");
258  *statement += mTable;
259 
260  if (mDatabaseType == DbType::MySQL && !mJoinedTables.isEmpty()) {
261  // for mysql we list all tables directly
262  *statement += QLatin1String(", ");
263  appendJoined(statement, mJoinedTables);
264  }
265 
266  *statement += QLatin1String(" SET ");
267  Q_ASSERT_X(mColumnValues.count() >= 1, "QueryBuilder::exec()", "At least one column needs to be changed");
268  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
269  const auto &[column, value] = mColumnValues.at(i);
270  *statement += column;
271  *statement += QLatin1String(" = ");
272  bindValue(statement, value);
273  if (i + 1 < c) {
274  *statement += QLatin1String(", ");
275  }
276  }
277 
278  if (mDatabaseType == DbType::PostgreSQL && !mJoinedTables.isEmpty()) {
279  // PSQL have this syntax
280  // FROM t1 JOIN t2 JOIN ...
281  *statement += QLatin1String(" FROM ");
282  appendJoined(statement, mJoinedTables, QLatin1String(" JOIN "));
283  }
284  break;
285  }
286  case Delete:
287  *statement += QLatin1String("DELETE FROM ");
288  *statement += mTable;
289  break;
290  default:
291  Q_ASSERT_X(false, "QueryBuilder::exec()", "Unknown enum value");
292  }
293 
294  if (!whereCondition.isEmpty()) {
295  *statement += QLatin1String(" WHERE ");
296  buildWhereCondition(statement, whereCondition);
297  }
298 
299  if (!mGroupColumns.isEmpty()) {
300  *statement += QLatin1String(" GROUP BY ");
301  appendJoined(statement, mGroupColumns);
302  }
303 
304  if (!mRootCondition[HavingCondition].isEmpty()) {
305  *statement += QLatin1String(" HAVING ");
306  buildWhereCondition(statement, mRootCondition[HavingCondition]);
307  }
308 
309  if (!mSortColumns.isEmpty()) {
310  Q_ASSERT_X(mType == Select, "QueryBuilder::exec()", "Order statements are only valid for SELECT queries");
311  *statement += QLatin1String(" ORDER BY ");
312  for (int i = 0, c = mSortColumns.size(); i < c; ++i) {
313  const auto &[column, order] = mSortColumns.at(i);
314  *statement += column;
315  *statement += sortOrderToString(order);
316  if (i + 1 < c) {
317  *statement += QLatin1String(", ");
318  }
319  }
320  }
321 
322  if (mLimit > 0) {
323  *statement += QLatin1String(" LIMIT ") + QString::number(mLimit);
324  }
325 
326  if (mType == Select && mForUpdate) {
327  if (mDatabaseType == DbType::Sqlite) {
328  // SQLite does not support SELECT ... FOR UPDATE syntax, because it does
329  // table-level locking
330  } else {
331  *statement += QLatin1String(" FOR UPDATE");
332  }
333  }
334 }
335 
337 {
338  QString statement;
339  statement.reserve(1024);
340  buildQuery(&statement);
341 
342 #ifndef QUERYBUILDER_UNITTEST
343  auto query = QueryCache::query(statement);
344  if (query.has_value()) {
345  mQuery = *query;
346  } else {
347  mQuery.clear();
348  if (!mQuery.prepare(statement)) {
349  qCCritical(AKONADISERVER_LOG) << "DATABASE ERROR while PREPARING QUERY:";
350  qCCritical(AKONADISERVER_LOG) << " Error code:" << mQuery.lastError().nativeErrorCode();
351  qCCritical(AKONADISERVER_LOG) << " DB error: " << mQuery.lastError().databaseText();
352  qCCritical(AKONADISERVER_LOG) << " Error text:" << mQuery.lastError().text();
353  qCCritical(AKONADISERVER_LOG) << " Query:" << statement;
354  return false;
355  }
356  QueryCache::insert(statement, mQuery);
357  }
358 
359  // too heavy debug info but worths to have from time to time
360  // qCDebug(AKONADISERVER_LOG) << "Executing query" << statement;
361  bool isBatch = false;
362  for (int i = 0; i < mBindValues.count(); ++i) {
363  mQuery.bindValue(QLatin1Char(':') + QString::number(i), mBindValues[i]);
364  if (!isBatch && static_cast<QMetaType::Type>(mBindValues[i].type()) == QMetaType::QVariantList) {
365  isBatch = true;
366  }
367  // qCDebug(AKONADISERVER_LOG) << QString::fromLatin1( ":%1" ).arg( i ) << mBindValues[i];
368  }
369 
370  bool ret;
371 
372  if (StorageDebugger::instance()->isSQLDebuggingEnabled()) {
373  QElapsedTimer t;
374  t.start();
375  if (isBatch) {
376  ret = mQuery.execBatch();
377  } else {
378  ret = mQuery.exec();
379  }
380  StorageDebugger::instance()->queryExecuted(reinterpret_cast<qint64>(DataStore::self()), mQuery, t.elapsed());
381  } else {
382  StorageDebugger::instance()->incSequence();
383  if (isBatch) {
384  ret = mQuery.execBatch();
385  } else {
386  ret = mQuery.exec();
387  }
388  }
389 
390  if (!ret) {
391  bool needsRetry = false;
392  // Handle transaction deadlocks and timeouts by attempting to replay the transaction.
393  if (mDatabaseType == DbType::PostgreSQL) {
394  const QString dbError = mQuery.lastError().databaseText();
395  if (dbError.contains(QLatin1String("40P01" /* deadlock_detected */))) {
396  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
397  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
398  needsRetry = true;
399  }
400  } else if (mDatabaseType == DbType::MySQL) {
401  const QString lastErrorStr = mQuery.lastError().nativeErrorCode();
402  const int error = lastErrorStr.isEmpty() ? -1 : lastErrorStr.toInt();
403  if (error == 1213 /* ER_LOCK_DEADLOCK */) {
404  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
405  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
406  needsRetry = true;
407  } else if (error == 1205 /* ER_LOCK_WAIT_TIMEOUT */) {
408  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction timeout, retrying transaction";
409  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
410  // Not sure retrying helps, maybe error is good enough.... but doesn't hurt to retry a few times before giving up.
411  needsRetry = true;
412  }
413  } else if (mDatabaseType == DbType::Sqlite && !DbType::isSystemSQLite(DataStore::self()->database())) {
414  const QString lastErrorStr = mQuery.lastError().nativeErrorCode();
415  const int error = lastErrorStr.isEmpty() ? -1 : lastErrorStr.toInt();
416  if (error == 6 /* SQLITE_LOCKED */) {
417  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
418  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
419  DataStore::self()->doRollback();
420  needsRetry = true;
421  } else if (error == 5 /* SQLITE_BUSY */) {
422  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction timeout, retrying transaction";
423  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
424  DataStore::self()->doRollback();
425  needsRetry = true;
426  }
427  } else if (mDatabaseType == DbType::Sqlite) {
428  // We can't have a transaction deadlock in SQLite when using driver shipped
429  // with Qt, because it does not support concurrent transactions and DataStore
430  // serializes them through a global lock.
431  }
432 
433  if (needsRetry) {
434  DataStore::self()->transactionKilledByDB();
435  throw DbDeadlockException(mQuery);
436  }
437 
438  qCCritical(AKONADISERVER_LOG) << "DATABASE ERROR:";
439  qCCritical(AKONADISERVER_LOG) << " Error code:" << mQuery.lastError().nativeErrorCode();
440  qCCritical(AKONADISERVER_LOG) << " DB error: " << mQuery.lastError().databaseText();
441  qCCritical(AKONADISERVER_LOG) << " Error text:" << mQuery.lastError().text();
442  qCCritical(AKONADISERVER_LOG) << " Values:" << mQuery.boundValues();
443  qCCritical(AKONADISERVER_LOG) << " Query:" << statement;
444  return false;
445  }
446 #else
447  mStatement = statement;
448 #endif
449  return true;
450 }
451 
453 {
454  mColumns << cols;
455 }
456 
458 {
459  mColumns << col;
460 }
461 
462 void QueryBuilder::addColumn(const Query::Case &caseStmt)
463 {
464  QString query;
465  buildCaseStatement(&query, caseStmt);
466  mColumns.append(query);
467 }
468 
469 void QueryBuilder::addAggregation(const QString &col, const QString &aggregate)
470 {
471  mColumns.append(aggregate + QLatin1Char('(') + col + QLatin1Char(')'));
472 }
473 
474 void QueryBuilder::addAggregation(const Query::Case &caseStmt, const QString &aggregate)
475 {
476  QString query(aggregate + QLatin1Char('('));
477  buildCaseStatement(&query, caseStmt);
478  query += QLatin1Char(')');
479 
480  mColumns.append(query);
481 }
482 
483 void QueryBuilder::bindValue(QString *query, const QVariant &value)
484 {
485  mBindValues << value;
486  *query += QLatin1Char(':') + QString::number(mBindValues.count() - 1);
487 }
488 
489 void QueryBuilder::buildWhereCondition(QString *query, const Query::Condition &cond)
490 {
491  if (!cond.isEmpty()) {
492  *query += QLatin1String("( ");
493  const QLatin1String glue = logicOperatorToString(cond.mCombineOp);
494  const Query::Condition::List &subConditions = cond.subConditions();
495  for (int i = 0, c = subConditions.size(); i < c; ++i) {
496  buildWhereCondition(query, subConditions.at(i));
497  if (i + 1 < c) {
498  *query += glue;
499  }
500  }
501  *query += QLatin1String(" )");
502  } else {
503  *query += cond.mColumn;
504  *query += compareOperatorToString(cond.mCompareOp);
505  if (cond.mComparedColumn.isEmpty()) {
506  if (cond.mComparedValue.isValid()) {
507  if (cond.mComparedValue.canConvert(QVariant::List)) {
508  *query += QLatin1String("( ");
509  const QVariantList &entries = cond.mComparedValue.toList();
510  Q_ASSERT_X(!entries.isEmpty(), "QueryBuilder::buildWhereCondition()", "No values given for IN condition.");
511  for (int i = 0, c = entries.size(); i < c; ++i) {
512  bindValue(query, entries.at(i));
513  if (i + 1 < c) {
514  *query += QLatin1String(", ");
515  }
516  }
517  *query += QLatin1String(" )");
518  } else {
519  bindValue(query, cond.mComparedValue);
520  }
521  } else {
522  *query += QLatin1String("NULL");
523  }
524  } else {
525  *query += cond.mComparedColumn;
526  }
527  }
528 }
529 
530 void QueryBuilder::buildCaseStatement(QString *query, const Query::Case &caseStmt)
531 {
532  *query += QLatin1String("CASE ");
533  Q_FOREACH (const auto &whenThen, caseStmt.mWhenThen) {
534  *query += QLatin1String("WHEN ");
535  buildWhereCondition(query, whenThen.first); // When
536  *query += QLatin1String(" THEN ") + whenThen.second; // then
537  }
538  if (!caseStmt.mElse.isEmpty()) {
539  *query += QLatin1String(" ELSE ") + caseStmt.mElse;
540  }
541  *query += QLatin1String(" END");
542 }
543 
545 {
546  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
547  mRootCondition[type].setSubQueryMode(op);
548 }
549 
551 {
552  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
553  mRootCondition[type].addCondition(condition);
554 }
555 
557 {
558  mSortColumns << qMakePair(column, order);
559 }
560 
562 {
563  Q_ASSERT(mType == Select);
564  mGroupColumns << column;
565 }
566 
568 {
569  Q_ASSERT(mType == Select);
570  mGroupColumns += columns;
571 }
572 
573 void QueryBuilder::setColumnValue(const QString &column, const QVariant &value)
574 {
575  mColumnValues << qMakePair(column, value);
576 }
577 
578 void QueryBuilder::setDistinct(bool distinct)
579 {
580  mDistinct = distinct;
581 }
582 
583 void QueryBuilder::setLimit(int limit)
584 {
585  mLimit = limit;
586 }
587 
589 {
590  mIdentificationColumn = column;
591 }
592 
594 {
595  if (mDatabaseType == DbType::PostgreSQL) {
596  query().next();
597  if (mIdentificationColumn.isEmpty()) {
598  return 0; // FIXME: Does this make sense?
599  }
600  return query().record().value(mIdentificationColumn).toLongLong();
601  } else {
602  const QVariant v = query().lastInsertId();
603  if (!v.isValid()) {
604  return -1;
605  }
606  bool ok;
607  const qint64 insertId = v.toLongLong(&ok);
608  if (!ok) {
609  return -1;
610  }
611  return insertId;
612  }
613  return -1;
614 }
615 
616 void QueryBuilder::setForUpdate(bool forUpdate)
617 {
618  mForUpdate = forUpdate;
619 }
void setDistinct(bool distinct)
Specify whether duplicates should be included in the result.
bool canConvert(int targetTypeId) const const
qlonglong toLongLong(bool *ok) const const
void addValueCondition(const QString &column, Query::CompareOperator op, const QVariant &value, ConditionType type=WhereCondition)
Add a WHERE or HAVING condition which compares a column with a given value.
void addCondition(const Condition &condition)
Add a WHERE condition.
Definition: query.cpp:54
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString databaseText() const const
QSqlQuery & query()
Returns the query, only valid after exec().
void setForUpdate(bool forUpdate=true)
Indicate to the database to acquire an exclusive lock on the rows already during SELECT statement...
bool isEmpty() const
Returns if there are sub conditions.
Definition: query.cpp:39
void setColumnValue(const QString &column, const QVariant &value)
Sets a column to the given value (only valid for INSERT and UPDATE queries).
void setDatabaseType(DbType::Type type)
Sets the database which should execute the query.
std::optional< QSqlQuery > query(const QString &queryStatement)
Returns the cached (and prepared) query for queryStatement.
Definition: querycache.cpp:95
QVariant value(int index) const const
Type
Supported database types.
Definition: dbtype.h:20
qint64 insertId()
Returns the ID of the newly created record (only valid for INSERT queries)
QList< QVariant > toList() const const
void addAggregation(const QString &col, const QString &aggregate)
Adds an aggregation statement.
JoinType
When the same table gets joined as both, Inner- and LeftJoin, it will be merged into a single InnerJo...
Definition: querybuilder.h:41
bool exec(const QString &query)
const T & at(int i) const const
void setSubQueryMode(Query::LogicOperator op, ConditionType type=WhereCondition)
Define how WHERE or HAVING conditions are combined.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
NOTE: only supported for UPDATE and SELECT queries.
Definition: querybuilder.h:43
bool isSystemSQLite(const QSqlDatabase &db)
Returns true when using QSQLITE driver shipped with Qt, FALSE otherwise.
Definition: dbtype.cpp:30
void addJoin(JoinType joinType, const QString &table, const Query::Condition &condition)
Join a table to the query.
int size() const const
void clear()
void addColumn(const QString &col)
Adds the given column to a select query.
void setIdentificationColumn(const QString &column)
Sets the column used for identification in an INSERT statement.
Condition::List subConditions() const
Returns the list of sub-conditions.
Definition: query.cpp:44
QMap< QString, QVariant > boundValues() const const
bool prepare(const QString &query)
add condition to WHERE part of the query
Definition: querybuilder.h:53
QString number(int n, int base)
void resize(int size)
int count(const T &value) const const
void append(const T &value)
SortOrder
Sort orders.
Definition: query.h:39
CompareOperator
Compare operators to be used in query conditions.
Definition: query.h:29
void addGroupColumn(const QString &column)
Add a GROUP BY column.
void bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType paramType)
QueryBuilder(const QString &table, QueryType type=Select)
Creates a new query builder.
add condition to HAVING part of the query NOTE: only supported for SELECT queries ...
Definition: querybuilder.h:56
int toInt(bool *ok, int base) const const
void addSortColumn(const QString &column, Query::SortOrder order=Query::Ascending)
Add sort column.
bool isEmpty() const const
bool execBatch(QSqlQuery::BatchExecutionMode mode)
bool isEmpty() const const
bool next()
void addColumnCondition(const QString &column, CompareOperator op, const QString &column2)
Add a WHERE condition which compares a column with another column.
Definition: query.cpp:22
ConditionType
Defines the place at which a condition should be evaluated.
Definition: querybuilder.h:51
void setForwardOnly(bool forward)
NOTE: only supported for SELECT queries.
Definition: querybuilder.h:45
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QSqlRecord record() const const
static DataStore * self()
Per thread singleton.
Definition: datastore.cpp:215
const T & at(int i) const const
void setSubQueryMode(LogicOperator op)
Set how sub-conditions should be combined, default is And.
Definition: query.cpp:49
void addGroupColumns(const QStringList &columns)
Add list of columns to GROUP BY.
bool isEmpty() const const
const QChar at(int position) const const
LogicOperator
Logic operations used to combine multiple query conditions.
Definition: query.h:34
int count(const T &value) const const
void insert(const QString &queryStatement, const QSqlQuery &query)
Insert query into the cache for queryStatement.
Definition: querycache.cpp:100
QSqlError lastError() const const
void setLimit(int limit)
Limits the amount of retrieved rows.
void reserve(int size)
QString left(int n) const const
bool isValid() const const
QString nativeErrorCode() const const
void addColumnCondition(const QString &column, Query::CompareOperator op, const QString &column2, ConditionType type=WhereCondition)
Add a WHERE or HAVING condition which compares a column with another column.
Represents a WHERE condition tree.
Definition: query.h:44
QString text() const const
void addColumns(const QStringList &cols)
Adds the given columns to a select query.
T & first()
This class handles all the database access.
Definition: datastore.h:95
int size() const const
qint64 elapsed() const const
Helper class to construct arbitrary SQL queries.
Definition: querybuilder.h:31
void addCondition(const Query::Condition &condition, ConditionType type=WhereCondition)
Add a WHERE condition.
QVariant lastInsertId() const const
const T value(const Key &key, const T &defaultValue) const const
bool exec()
Executes the query, returns true on success.
void addValueCondition(const QString &column, CompareOperator op, const QVariant &value)
Add a WHERE condition which compares a column with a given value.
Definition: query.cpp:12
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Mar 8 2021 23:16:47 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.