Akonadi

querybuilder.cpp
1 /*
2  Copyright (c) 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 <QSqlRecord>
20 #include <QSqlError>
21 #include <QElapsedTimer>
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)) ||
114  (joinType == LeftJoin && mType == Select));
115 
116  if (mJoinedTables.contains(table)) {
117  // InnerJoin is more restrictive than a LeftJoin, hence use that in doubt
118  mJoins[table].first = qMin(joinType, mJoins.value(table).first);
119  mJoins[table].second.addCondition(condition);
120  } else {
121  mJoins[table] = qMakePair(joinType, condition);
122  mJoinedTables << table;
123  }
124 }
125 
126 void QueryBuilder::addJoin(JoinType joinType, const QString &table, const QString &col1, const QString &col2)
127 {
128  Query::Condition condition;
129  condition.addColumnCondition(col1, Query::Equals, col2);
130  addJoin(joinType, table, condition);
131 }
132 
134 {
135  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
136  mRootCondition[type].addValueCondition(column, op, value);
137 }
138 
140 {
141  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
142  mRootCondition[type].addColumnCondition(column, op, column2);
143 }
144 
146 {
147  return mQuery;
148 }
149 
150 void QueryBuilder::sqliteAdaptUpdateJoin(Query::Condition &condition)
151 {
152  // FIXME: This does not cover all cases by far. It however can handle most
153  // (probably all) of the update-join queries we do in Akonadi and convert them
154  // properly into a SQLite-compatible query. Better than nothing ;-)
155 
156  if (!condition.mSubConditions.isEmpty()) {
157  for (int i = condition.mSubConditions.count() - 1; i >= 0; --i) {
158  sqliteAdaptUpdateJoin(condition.mSubConditions[i]);
159  }
160  return;
161  }
162 
163  QString table;
164  if (condition.mColumn.contains(QLatin1Char('.'))) {
165  table = condition.mColumn.left(condition.mColumn.indexOf(QLatin1Char('.')));
166  } else {
167  return;
168  }
169 
170  if (!mJoinedTables.contains(table)) {
171  return;
172  }
173 
174  const auto &[type, joinCondition] = mJoins.value(table);
175 
176  QueryBuilder qb(table, Select);
177  qb.addColumn(condition.mColumn);
178  qb.addCondition(joinCondition);
179 
180  // Convert the subquery to string
181  condition.mColumn.reserve(1024);
182  condition.mColumn.resize(0);
183  condition.mColumn += QLatin1String("( ");
184  qb.buildQuery(&condition.mColumn);
185  condition.mColumn += QLatin1String(" )");
186 }
187 
188 void QueryBuilder::buildQuery(QString *statement)
189 {
190  // we add the ON conditions of Inner Joins in a Update query here
191  // but don't want to change the mRootCondition on each exec().
192  Query::Condition whereCondition = mRootCondition[WhereCondition];
193 
194  switch (mType) {
195  case Select:
196  // Enable forward-only on all SELECT queries, since we never need to
197  // iterate backwards. This is a memory optimization.
198  mQuery.setForwardOnly(true);
199  *statement += QLatin1String("SELECT ");
200  if (mDistinct) {
201  *statement += QLatin1String("DISTINCT ");
202  }
203  Q_ASSERT_X(mColumns.count() > 0, "QueryBuilder::exec()", "No columns specified");
204  appendJoined(statement, mColumns);
205  *statement += QLatin1String(" FROM ");
206  *statement += mTable;
207  for (const QString &joinedTable : qAsConst(mJoinedTables)) {
208  const auto &[joinType, joinCond] = mJoins.value(joinedTable);
209  switch (joinType) {
210  case LeftJoin:
211  *statement += QLatin1String(" LEFT JOIN ");
212  break;
213  case InnerJoin:
214  *statement += QLatin1String(" INNER JOIN ");
215  break;
216  }
217  *statement += joinedTable;
218  *statement += QLatin1String(" ON ");
219  buildWhereCondition(statement, joinCond);
220  }
221  break;
222  case Insert: {
223  *statement += QLatin1String("INSERT INTO ");
224  *statement += mTable;
225  *statement += QLatin1String(" (");
226  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
227  *statement += mColumnValues.at(i).first;
228  if (i + 1 < c) {
229  *statement += QLatin1String(", ");
230  }
231  }
232  *statement += QLatin1String(") VALUES (");
233  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
234  bindValue(statement, mColumnValues.at(i).second);
235  if (i + 1 < c) {
236  *statement += QLatin1String(", ");
237  }
238  }
239  *statement += QLatin1Char(')');
240  if (mDatabaseType == DbType::PostgreSQL && !mIdentificationColumn.isEmpty()) {
241  *statement += QLatin1String(" RETURNING ") + mIdentificationColumn;
242  }
243  break;
244  }
245  case Update: {
246  // put the ON condition into the WHERE part of the UPDATE query
247  if (mDatabaseType != DbType::Sqlite) {
248  for (const QString &table : qAsConst(mJoinedTables)) {
249  const auto &[joinType, joinCond] = mJoins.value(table);
250  Q_ASSERT(joinType == InnerJoin);
251  whereCondition.addCondition(joinCond);
252  }
253  } else {
254  // Note: this will modify the whereCondition
255  sqliteAdaptUpdateJoin(whereCondition);
256  }
257 
258  *statement += QLatin1String("UPDATE ");
259  *statement += mTable;
260 
261  if (mDatabaseType == DbType::MySQL && !mJoinedTables.isEmpty()) {
262  // for mysql we list all tables directly
263  *statement += QLatin1String(", ");
264  appendJoined(statement, mJoinedTables);
265  }
266 
267  *statement += QLatin1String(" SET ");
268  Q_ASSERT_X(mColumnValues.count() >= 1, "QueryBuilder::exec()", "At least one column needs to be changed");
269  for (int i = 0, c = mColumnValues.size(); i < c; ++i) {
270  const auto &[column, value] = mColumnValues.at(i);
271  *statement += column;
272  *statement += QLatin1String(" = ");
273  bindValue(statement, value);
274  if (i + 1 < c) {
275  *statement += QLatin1String(", ");
276  }
277  }
278 
279  if (mDatabaseType == DbType::PostgreSQL && !mJoinedTables.isEmpty()) {
280  // PSQL have this syntax
281  // FROM t1 JOIN t2 JOIN ...
282  *statement += QLatin1String(" FROM ");
283  appendJoined(statement, mJoinedTables, QLatin1String(" JOIN "));
284  }
285  break;
286  }
287  case Delete:
288  *statement += QLatin1String("DELETE FROM ");
289  *statement += mTable;
290  break;
291  default:
292  Q_ASSERT_X(false, "QueryBuilder::exec()", "Unknown enum value");
293  }
294 
295  if (!whereCondition.isEmpty()) {
296  *statement += QLatin1String(" WHERE ");
297  buildWhereCondition(statement, whereCondition);
298  }
299 
300  if (!mGroupColumns.isEmpty()) {
301  *statement += QLatin1String(" GROUP BY ");
302  appendJoined(statement, mGroupColumns);
303  }
304 
305  if (!mRootCondition[HavingCondition].isEmpty()) {
306  *statement += QLatin1String(" HAVING ");
307  buildWhereCondition(statement, mRootCondition[HavingCondition]);
308  }
309 
310  if (!mSortColumns.isEmpty()) {
311  Q_ASSERT_X(mType == Select, "QueryBuilder::exec()", "Order statements are only valid for SELECT queries");
312  *statement += QLatin1String(" ORDER BY ");
313  for (int i = 0, c = mSortColumns.size(); i < c; ++i) {
314  const auto &[column, order] = mSortColumns.at(i);
315  *statement += column;
316  *statement += sortOrderToString(order);
317  if (i + 1 < c) {
318  *statement += QLatin1String(", ");
319  }
320  }
321  }
322 
323  if (mLimit > 0) {
324  *statement += QLatin1String(" LIMIT ") + QString::number(mLimit);
325  }
326 
327  if (mType == Select && mForUpdate) {
328  if (mDatabaseType == DbType::Sqlite) {
329  // SQLite does not support SELECT ... FOR UPDATE syntax, because it does
330  // table-level locking
331  } else {
332  *statement += QLatin1String(" FOR UPDATE");
333  }
334  }
335 }
336 
338 {
339  QString statement;
340  statement.reserve(1024);
341  buildQuery(&statement);
342 
343 #ifndef QUERYBUILDER_UNITTEST
344  auto query = QueryCache::query(statement);
345  if (query.has_value()) {
346  mQuery = *query;
347  } else {
348  mQuery.clear();
349  if (!mQuery.prepare(statement)) {
350  qCCritical(AKONADISERVER_LOG) << "DATABASE ERROR while PREPARING QUERY:";
351  qCCritical(AKONADISERVER_LOG) << " Error code:" << mQuery.lastError().nativeErrorCode();
352  qCCritical(AKONADISERVER_LOG) << " DB error: " << mQuery.lastError().databaseText();
353  qCCritical(AKONADISERVER_LOG) << " Error text:" << mQuery.lastError().text();
354  qCCritical(AKONADISERVER_LOG) << " Query:" << statement;
355  return false;
356  }
357  QueryCache::insert(statement, mQuery);
358  }
359 
360  //too heavy debug info but worths to have from time to time
361  //qCDebug(AKONADISERVER_LOG) << "Executing query" << statement;
362  bool isBatch = false;
363  for (int i = 0; i < mBindValues.count(); ++i) {
364  mQuery.bindValue(QLatin1Char(':') + QString::number(i), mBindValues[i]);
365  if (!isBatch && static_cast<QMetaType::Type>(mBindValues[i].type()) == QMetaType::QVariantList) {
366  isBatch = true;
367  }
368  //qCDebug(AKONADISERVER_LOG) << QString::fromLatin1( ":%1" ).arg( i ) << mBindValues[i];
369  }
370 
371  bool ret;
372 
373  if (StorageDebugger::instance()->isSQLDebuggingEnabled()) {
374  QElapsedTimer t;
375  t.start();
376  if (isBatch) {
377  ret = mQuery.execBatch();
378  } else {
379  ret = mQuery.exec();
380  }
381  StorageDebugger::instance()->queryExecuted(reinterpret_cast<qint64>(DataStore::self()),
382  mQuery, t.elapsed());
383  } else {
384  StorageDebugger::instance()->incSequence();
385  if (isBatch) {
386  ret = mQuery.execBatch();
387  } else {
388  ret = mQuery.exec();
389  }
390  }
391 
392  if (!ret) {
393  bool needsRetry = false;
394  // Handle transaction deadlocks and timeouts by attempting to replay the transaction.
395  if (mDatabaseType == DbType::PostgreSQL) {
396  const QString dbError = mQuery.lastError().databaseText();
397  if (dbError.contains(QLatin1String("40P01" /* deadlock_detected */))) {
398  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
399  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
400  needsRetry = true;
401  }
402  } else if (mDatabaseType == DbType::MySQL) {
403  const QString lastErrorStr = mQuery.lastError().nativeErrorCode();
404  const int error = lastErrorStr.isEmpty() ? -1 : lastErrorStr.toInt();
405  if (error == 1213 /* ER_LOCK_DEADLOCK */) {
406  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
407  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
408  needsRetry = true;
409  } else if (error == 1205 /* ER_LOCK_WAIT_TIMEOUT */) {
410  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction timeout, retrying transaction";
411  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
412  // Not sure retrying helps, maybe error is good enough.... but doesn't hurt to retry a few times before giving up.
413  needsRetry = true;
414  }
415  } else if (mDatabaseType == DbType::Sqlite && !DbType::isSystemSQLite(DataStore::self()->database())) {
416  const QString lastErrorStr = mQuery.lastError().nativeErrorCode();
417  const int error = lastErrorStr.isEmpty() ? -1 : lastErrorStr.toInt();
418  if (error == 6 /* SQLITE_LOCKED */) {
419  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
420  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
421  DataStore::self()->doRollback();
422  needsRetry = true;
423  } else if (error == 5 /* SQLITE_BUSY */) {
424  qCWarning(AKONADISERVER_LOG) << "QueryBuilder::exec(): database reported transaction timeout, retrying transaction";
425  qCWarning(AKONADISERVER_LOG) << mQuery.lastError().text();
426  DataStore::self()->doRollback();
427  needsRetry = true;
428  }
429  } else if (mDatabaseType == DbType::Sqlite) {
430  // We can't have a transaction deadlock in SQLite when using driver shipped
431  // with Qt, because it does not support concurrent transactions and DataStore
432  // serializes them through a global lock.
433  }
434 
435  if (needsRetry) {
436  DataStore::self()->transactionKilledByDB();
437  throw DbDeadlockException(mQuery);
438  }
439 
440  qCCritical(AKONADISERVER_LOG) << "DATABASE ERROR:";
441  qCCritical(AKONADISERVER_LOG) << " Error code:" << mQuery.lastError().nativeErrorCode();
442  qCCritical(AKONADISERVER_LOG) << " DB error: " << mQuery.lastError().databaseText();
443  qCCritical(AKONADISERVER_LOG) << " Error text:" << mQuery.lastError().text();
444  qCCritical(AKONADISERVER_LOG) << " Values:" << mQuery.boundValues();
445  qCCritical(AKONADISERVER_LOG) << " Query:" << statement;
446  return false;
447  }
448 #else
449  mStatement = statement;
450 #endif
451  return true;
452 }
453 
455 {
456  mColumns << cols;
457 }
458 
460 {
461  mColumns << col;
462 }
463 
464 void QueryBuilder::addColumn(const Query::Case &caseStmt)
465 {
466  QString query;
467  buildCaseStatement(&query, caseStmt);
468  mColumns.append(query);
469 }
470 
471 void QueryBuilder::addAggregation(const QString &col, const QString &aggregate)
472 {
473  mColumns.append(aggregate + QLatin1Char('(') + col + QLatin1Char(')'));
474 }
475 
476 void QueryBuilder::addAggregation(const Query::Case &caseStmt, const QString &aggregate)
477 {
478  QString query(aggregate + QLatin1Char('('));
479  buildCaseStatement(&query, caseStmt);
480  query += QLatin1Char(')');
481 
482  mColumns.append(query);
483 }
484 
485 void QueryBuilder::bindValue(QString *query, const QVariant &value)
486 {
487  mBindValues << value;
488  *query += QLatin1Char(':') + QString::number(mBindValues.count() - 1);
489 }
490 
491 void QueryBuilder::buildWhereCondition(QString *query, const Query::Condition &cond)
492 {
493  if (!cond.isEmpty()) {
494  *query += QLatin1String("( ");
495  const QLatin1String glue = logicOperatorToString(cond.mCombineOp);
496  const Query::Condition::List &subConditions = cond.subConditions();
497  for (int i = 0, c = subConditions.size(); i < c; ++i) {
498  buildWhereCondition(query, subConditions.at(i));
499  if (i + 1 < c) {
500  *query += glue;
501  }
502  }
503  *query += QLatin1String(" )");
504  } else {
505  *query += cond.mColumn;
506  *query += compareOperatorToString(cond.mCompareOp);
507  if (cond.mComparedColumn.isEmpty()) {
508  if (cond.mComparedValue.isValid()) {
509  if (cond.mComparedValue.canConvert(QVariant::List)) {
510  *query += QLatin1String("( ");
511  const QVariantList &entries = cond.mComparedValue.toList();
512  Q_ASSERT_X(!entries.isEmpty(),
513  "QueryBuilder::buildWhereCondition()", "No values given for IN condition.");
514  for (int i = 0, c = entries.size(); i < c; ++i) {
515  bindValue(query, entries.at(i));
516  if (i + 1 < c) {
517  *query += QLatin1String(", ");
518  }
519  }
520  *query += QLatin1String(" )");
521  } else {
522  bindValue(query, cond.mComparedValue);
523  }
524  } else {
525  *query += QLatin1String("NULL");
526  }
527  } else {
528  *query += cond.mComparedColumn;
529  }
530  }
531 }
532 
533 void QueryBuilder::buildCaseStatement(QString *query, const Query::Case &caseStmt)
534 {
535  *query += QLatin1String("CASE ");
536  Q_FOREACH (const auto &whenThen, caseStmt.mWhenThen) {
537  *query += QLatin1String("WHEN ");
538  buildWhereCondition(query, whenThen.first); // When
539  *query += QLatin1String(" THEN ") + whenThen.second; // then
540  }
541  if (!caseStmt.mElse.isEmpty()) {
542  *query += QLatin1String(" ELSE ") + caseStmt.mElse;
543  }
544  *query += QLatin1String(" END");
545 }
546 
548 {
549  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
550  mRootCondition[type].setSubQueryMode(op);
551 }
552 
554 {
555  Q_ASSERT(type == WhereCondition || (type == HavingCondition && mType == Select));
556  mRootCondition[type].addCondition(condition);
557 }
558 
560 {
561  mSortColumns << qMakePair(column, order);
562 }
563 
565 {
566  Q_ASSERT(mType == Select);
567  mGroupColumns << column;
568 }
569 
571 {
572  Q_ASSERT(mType == Select);
573  mGroupColumns += columns;
574 }
575 
576 void QueryBuilder::setColumnValue(const QString &column, const QVariant &value)
577 {
578  mColumnValues << qMakePair(column, value);
579 }
580 
581 void QueryBuilder::setDistinct(bool distinct)
582 {
583  mDistinct = distinct;
584 }
585 
586 void QueryBuilder::setLimit(int limit)
587 {
588  mLimit = limit;
589 }
590 
592 {
593  mIdentificationColumn = column;
594 }
595 
597 {
598  if (mDatabaseType == DbType::PostgreSQL) {
599  query().next();
600  if (mIdentificationColumn.isEmpty()) {
601  return 0; // FIXME: Does this make sense?
602  }
603  return query().record().value(mIdentificationColumn).toLongLong();
604  } else {
605  const QVariant v = query().lastInsertId();
606  if (!v.isValid()) {
607  return -1;
608  }
609  bool ok;
610  const qint64 insertId = v.toLongLong(&ok);
611  if (!ok) {
612  return -1;
613  }
614  return insertId;
615  }
616  return -1;
617 }
618 
619 void QueryBuilder::setForUpdate(bool forUpdate)
620 {
621  mForUpdate = forUpdate;
622 }
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:22
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:47
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:49
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:59
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:56
CompareOperator
Compare operators to be used in query conditions.
Definition: query.h:31
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:62
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:57
void setForwardOnly(bool forward)
NOTE: only supported for SELECT queries.
Definition: querybuilder.h:51
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QSqlRecord record() const const
static DataStore * self()
Per thread singleton.
Definition: datastore.cpp:219
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:48
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:64
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:32
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-2020 The KDE developers.
Generated on Fri Jul 10 2020 23:14:20 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.