Akonadi

datastore.cpp
1 /***************************************************************************
2  * SPDX-FileCopyrightText: 2006 Andreas Gungl <[email protected]> *
3  * SPDX-FileCopyrightText: 2007 Robert Zwerus <[email protected]> *
4  * *
5  * SPDX-License-Identifier: LGPL-2.0-or-later *
6  ***************************************************************************/
7 
8 #include "datastore.h"
9 
10 #include "akonadi.h"
11 #include "akonadischema.h"
12 #include "akonadiserver_debug.h"
13 #include "collectionqueryhelper.h"
14 #include "collectionstatistics.h"
15 #include "countquerybuilder.h"
16 #include "dbconfig.h"
17 #include "dbinitializer.h"
18 #include "dbupdater.h"
19 #include "handler.h"
20 #include "handlerhelper.h"
21 #include "notificationmanager.h"
22 #include "parthelper.h"
23 #include "parttypehelper.h"
24 #include "querycache.h"
25 #include "queryhelper.h"
26 #include "selectquerybuilder.h"
27 #include "storagedebugger.h"
28 #include "tracer.h"
29 #include "transaction.h"
30 #include <utils.h>
31 
32 #include <private/externalpartstorage_p.h>
33 
34 #include <QCoreApplication>
35 #include <QElapsedTimer>
36 #include <QFile>
37 #include <QSqlDriver>
38 #include <QSqlQuery>
39 #include <QString>
40 #include <QStringList>
41 #include <QThread>
42 #include <QTimer>
43 #include <QUuid>
44 #include <QVariant>
45 
46 #include <functional>
47 
48 using namespace Akonadi;
49 using namespace Akonadi::Server;
50 
51 bool DataStore::s_hasForeignKeyConstraints = false;
52 QMutex DataStore::sTransactionMutex = {};
53 
54 static QThreadStorage<DataStore *> sInstances;
55 
56 #define TRANSACTION_MUTEX_LOCK \
57  if (DbType::isSystemSQLite(m_database)) \
58  sTransactionMutex.lock()
59 #define TRANSACTION_MUTEX_UNLOCK \
60  if (DbType::isSystemSQLite(m_database)) \
61  sTransactionMutex.unlock()
62 
63 #define setBoolPtr(ptr, val) \
64  { \
65  if ((ptr)) { \
66  *(ptr) = (val); \
67  } \
68  }
69 
70 std::unique_ptr<DataStoreFactory> DataStore::sFactory;
71 
72 void DataStore::setFactory(std::unique_ptr<DataStoreFactory> factory)
73 {
74  sFactory = std::move(factory);
75 }
76 
77 /***************************************************************************
78  * DataStore *
79  ***************************************************************************/
80 DataStore::DataStore(AkonadiServer &akonadi)
81  : m_akonadi(akonadi)
82  , m_dbOpened(false)
83  , m_transactionLevel(0)
84  , m_keepAliveTimer(nullptr)
85 {
86  if (DbConfig::configuredDatabase()->driverName() == QLatin1String("QMYSQL")) {
87  // Send a dummy query to MySQL every 1 hour to keep the connection alive,
88  // otherwise MySQL just drops the connection and our subsequent queries fail
89  // without properly reporting the error
90  m_keepAliveTimer = new QTimer(this);
91  m_keepAliveTimer->setInterval(3600 * 1000);
92  QObject::connect(m_keepAliveTimer, &QTimer::timeout, this, &DataStore::sendKeepAliveQuery);
93  }
94 }
95 
97 {
98  Q_ASSERT_X(!m_dbOpened, "DataStore", "Attempting to destroy DataStore with opened DB connection. Call close() first!");
99 }
100 
102 {
103  m_connectionName = QUuid::createUuid().toString() + QString::number(reinterpret_cast<qulonglong>(QThread::currentThread()));
104  Q_ASSERT(!QSqlDatabase::contains(m_connectionName));
105 
106  m_database = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), m_connectionName);
107  DbConfig::configuredDatabase()->apply(m_database);
108 
109  if (!m_database.isValid()) {
110  m_dbOpened = false;
111  return;
112  }
113  m_dbOpened = m_database.open();
114 
115  if (!m_dbOpened) {
116  debugLastDbError("Cannot open database.");
117  } else {
118  qCDebug(AKONADISERVER_LOG) << "Database" << m_database.databaseName() << "opened using driver" << m_database.driverName();
119  }
120 
121  StorageDebugger::instance()->addConnection(reinterpret_cast<qint64>(this), QThread::currentThread()->objectName());
122  connect(QThread::currentThread(), &QThread::objectNameChanged, this, [this](const QString &name) {
123  if (!name.isEmpty()) {
124  StorageDebugger::instance()->changeConnection(reinterpret_cast<qint64>(this), name);
125  }
126  });
127 
129 
130  if (m_keepAliveTimer) {
131  m_keepAliveTimer->start();
132  }
133 }
134 
136 {
137  if (!m_dbOpened) {
138  open();
139  }
140  return m_database;
141 }
142 
144 {
145  if (m_keepAliveTimer) {
146  m_keepAliveTimer->stop();
147  }
148 
149  if (!m_dbOpened) {
150  return;
151  }
152 
153  if (inTransaction()) {
154  // By setting m_transactionLevel to '1' here, we skip all nested transactions
155  // and rollback the outermost transaction.
156  m_transactionLevel = 1;
158  }
159 
161  m_database.close();
162  m_database = QSqlDatabase();
163  QSqlDatabase::removeDatabase(m_connectionName);
164 
165  StorageDebugger::instance()->removeConnection(reinterpret_cast<qint64>(this));
166 
167  m_dbOpened = false;
168 }
169 
171 {
173 
174  AkonadiSchema schema;
176  if (!initializer->run()) {
177  qCCritical(AKONADISERVER_LOG) << initializer->errorMsg();
178  return false;
179  }
180  s_hasForeignKeyConstraints = initializer->hasForeignKeyConstraints();
181 
182  if (QFile::exists(QStringLiteral(":dbupdate.xml"))) {
183  DbUpdater updater(database(), QStringLiteral(":dbupdate.xml"));
184  if (!updater.run()) {
185  return false;
186  }
187  } else {
188  qCWarning(AKONADISERVER_LOG) << "Warning: dbupdate.xml not found, skipping updates";
189  }
190 
191  if (!initializer->updateIndexesAndConstraints()) {
192  qCCritical(AKONADISERVER_LOG) << initializer->errorMsg();
193  return false;
194  }
195 
196  // enable caching for some tables
197  MimeType::enableCache(true);
198  Flag::enableCache(true);
199  Resource::enableCache(true);
200  Collection::enableCache(true);
201  PartType::enableCache(true);
202 
203  return true;
204 }
205 
207 {
208  if (!mNotificationCollector) {
209  mNotificationCollector = std::make_unique<NotificationCollector>(m_akonadi, this);
210  }
211 
212  return mNotificationCollector.get();
213 }
214 
216 {
217  if (!sInstances.hasLocalData()) {
218  sInstances.setLocalData(sFactory->createStore());
219  }
220  return sInstances.localData();
221 }
222 
224 {
225  return sInstances.hasLocalData();
226 }
227 
228 /* --- ItemFlags ----------------------------------------------------- */
229 
230 bool DataStore::setItemsFlags(const PimItem::List &items,
231  const QVector<Flag> *currentFlags,
232  const QVector<Flag> &newFlags,
233  bool *flagsChanged,
234  const Collection &col_,
235  bool silent)
236 {
237  QSet<QString> removedFlags;
238  QSet<QString> addedFlags;
239  QVariantList insIds;
240  QVariantList insFlags;
241  Query::Condition delConds(Query::Or);
242  Collection col = col_;
243 
244  setBoolPtr(flagsChanged, false);
245 
246  for (const PimItem &item : items) {
247  const Flag::List itemFlags = currentFlags ? *currentFlags : item.flags(); // optimization
248  for (const Flag &flag : itemFlags) {
249  if (!newFlags.contains(flag)) {
250  removedFlags << flag.name();
251  Query::Condition cond;
252  cond.addValueCondition(PimItemFlagRelation::leftFullColumnName(), Query::Equals, item.id());
253  cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(), Query::Equals, flag.id());
254  delConds.addCondition(cond);
255  }
256  }
257 
258  for (const Flag &flag : newFlags) {
259  if (!itemFlags.contains(flag)) {
260  addedFlags << flag.name();
261  insIds << item.id();
262  insFlags << flag.id();
263  }
264  }
265 
266  if (col.id() == -1) {
267  col.setId(item.collectionId());
268  } else if (col.id() != item.collectionId()) {
269  col.setId(-2);
270  }
271  }
272 
273  if (!removedFlags.empty()) {
274  QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Delete);
275  qb.addCondition(delConds);
276  if (!qb.exec()) {
277  return false;
278  }
279  }
280 
281  if (!addedFlags.empty()) {
282  QueryBuilder qb2(PimItemFlagRelation::tableName(), QueryBuilder::Insert);
283  qb2.setColumnValue(PimItemFlagRelation::leftColumn(), insIds);
284  qb2.setColumnValue(PimItemFlagRelation::rightColumn(), insFlags);
285  qb2.setIdentificationColumn(QString());
286  if (!qb2.exec()) {
287  return false;
288  }
289  }
290 
291  if (!silent && (!addedFlags.isEmpty() || !removedFlags.isEmpty())) {
292  QSet<QByteArray> addedFlagsBa;
293  QSet<QByteArray> removedFlagsBa;
294  for (const auto &addedFlag : std::as_const(addedFlags)) {
295  addedFlagsBa.insert(addedFlag.toLatin1());
296  }
297  for (const auto &removedFlag : std::as_const(removedFlags)) {
298  removedFlagsBa.insert(removedFlag.toLatin1());
299  }
300  notificationCollector()->itemsFlagsChanged(items, addedFlagsBa, removedFlagsBa, col);
301  }
302 
303  setBoolPtr(flagsChanged, (addedFlags != removedFlags));
304 
305  return true;
306 }
307 
308 bool DataStore::doAppendItemsFlag(const PimItem::List &items, const Flag &flag, const QSet<Entity::Id> &existing, const Collection &col_, bool silent)
309 {
310  Collection col = col_;
311  QVariantList flagIds;
312  QVariantList appendIds;
313  PimItem::List appendItems;
314  for (const PimItem &item : items) {
315  if (existing.contains(item.id())) {
316  continue;
317  }
318 
319  flagIds << flag.id();
320  appendIds << item.id();
321  appendItems << item;
322 
323  if (col.id() == -1) {
324  col.setId(item.collectionId());
325  } else if (col.id() != item.collectionId()) {
326  col.setId(-2);
327  }
328  }
329 
330  if (appendItems.isEmpty()) {
331  return true; // all items have the desired flags already
332  }
333 
334  QueryBuilder qb2(PimItemFlagRelation::tableName(), QueryBuilder::Insert);
335  qb2.setColumnValue(PimItemFlagRelation::leftColumn(), appendIds);
336  qb2.setColumnValue(PimItemFlagRelation::rightColumn(), flagIds);
337  qb2.setIdentificationColumn(QString());
338  if (!qb2.exec()) {
339  qCWarning(AKONADISERVER_LOG) << "Failed to append flag" << flag.name() << "to Items" << appendIds;
340  return false;
341  }
342 
343  if (!silent) {
344  notificationCollector()->itemsFlagsChanged(appendItems, {flag.name().toLatin1()}, {}, col);
345  }
346 
347  return true;
348 }
349 
350 bool DataStore::appendItemsFlags(const PimItem::List &items,
351  const QVector<Flag> &flags,
352  bool *flagsChanged,
353  bool checkIfExists,
354  const Collection &col,
355  bool silent)
356 {
357  QVariantList itemsIds;
358  itemsIds.reserve(items.count());
359  for (const PimItem &item : items) {
360  itemsIds.append(item.id());
361  }
362 
363  setBoolPtr(flagsChanged, false);
364 
365  for (const Flag &flag : flags) {
366  QSet<PimItem::Id> existing;
367  if (checkIfExists) {
368  QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Select);
369  Query::Condition cond;
370  cond.addValueCondition(PimItemFlagRelation::rightColumn(), Query::Equals, flag.id());
371  cond.addValueCondition(PimItemFlagRelation::leftColumn(), Query::In, itemsIds);
372  qb.addColumn(PimItemFlagRelation::leftColumn());
373  qb.addCondition(cond);
374 
375  if (!qb.exec()) {
376  qCWarning(AKONADISERVER_LOG) << "Failed to retrieve existing flags for Items " << itemsIds;
377  return false;
378  }
379 
380  QSqlQuery query = qb.query();
381  if (query.driver()->hasFeature(QSqlDriver::QuerySize)) {
382  // The query size feature is not supported by the sqllite driver
383  if (query.size() == items.count()) {
384  continue;
385  }
386  setBoolPtr(flagsChanged, true);
387  }
388 
389  while (query.next()) {
390  existing << query.value(0).value<PimItem::Id>();
391  }
392  if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) {
393  if (existing.size() != items.count()) {
394  setBoolPtr(flagsChanged, true);
395  }
396  }
397  query.finish();
398  }
399 
400  if (!doAppendItemsFlag(items, flag, existing, col, silent)) {
401  return false;
402  }
403  }
404 
405  return true;
406 }
407 
408 bool DataStore::removeItemsFlags(const PimItem::List &items, const QVector<Flag> &flags, bool *flagsChanged, const Collection &col_, bool silent)
409 {
410  Collection col = col_;
411  QSet<QString> removedFlags;
412  QVariantList itemsIds;
413  QVariantList flagsIds;
414 
415  setBoolPtr(flagsChanged, false);
416  itemsIds.reserve(items.count());
417 
418  for (const PimItem &item : items) {
419  itemsIds << item.id();
420  if (col.id() == -1) {
421  col.setId(item.collectionId());
422  } else if (col.id() != item.collectionId()) {
423  col.setId(-2);
424  }
425  for (int i = 0; i < flags.count(); ++i) {
426  const QString flagName = flags[i].name();
427  if (!removedFlags.contains(flagName)) {
428  flagsIds << flags[i].id();
429  removedFlags << flagName;
430  }
431  }
432  }
433 
434  // Delete all given flags from all given items in one go
435  QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Delete);
436  Query::Condition cond(Query::And);
437  cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(), Query::In, flagsIds);
438  cond.addValueCondition(PimItemFlagRelation::leftFullColumnName(), Query::In, itemsIds);
439  qb.addCondition(cond);
440  if (!qb.exec()) {
441  qCWarning(AKONADISERVER_LOG) << "Failed to remove flags" << flags << "from Items" << itemsIds;
442  return false;
443  }
444 
445  if (qb.query().numRowsAffected() != 0) {
446  setBoolPtr(flagsChanged, true);
447  if (!silent) {
448  QSet<QByteArray> removedFlagsBa;
449  for (const auto &remoteFlag : std::as_const(removedFlags)) {
450  removedFlagsBa.insert(remoteFlag.toLatin1());
451  }
452  notificationCollector()->itemsFlagsChanged(items, {}, removedFlagsBa, col);
453  }
454  }
455 
456  return true;
457 }
458 
459 /* --- ItemTags ----------------------------------------------------- */
460 
461 bool DataStore::setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent)
462 {
463  QSet<qint64> removedTags;
464  QSet<qint64> addedTags;
465  QVariantList insIds;
466  QVariantList insTags;
467  Query::Condition delConds(Query::Or);
468 
469  setBoolPtr(tagsChanged, false);
470 
471  for (const PimItem &item : items) {
472  const Tag::List itemTags = item.tags();
473  for (const Tag &tag : itemTags) {
474  if (!tags.contains(tag)) {
475  // Remove tags from items that had it set
476  removedTags << tag.id();
477  Query::Condition cond;
478  cond.addValueCondition(PimItemTagRelation::leftFullColumnName(), Query::Equals, item.id());
479  cond.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::Equals, tag.id());
480  delConds.addCondition(cond);
481  }
482  }
483 
484  for (const Tag &tag : tags) {
485  if (!itemTags.contains(tag)) {
486  // Add tags to items that did not have the tag
487  addedTags << tag.id();
488  insIds << item.id();
489  insTags << tag.id();
490  }
491  }
492  }
493 
494  if (!removedTags.empty()) {
495  QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Delete);
496  qb.addCondition(delConds);
497  if (!qb.exec()) {
498  qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << removedTags << "from Items";
499  return false;
500  }
501  }
502 
503  if (!addedTags.empty()) {
504  QueryBuilder qb2(PimItemTagRelation::tableName(), QueryBuilder::Insert);
505  qb2.setColumnValue(PimItemTagRelation::leftColumn(), insIds);
506  qb2.setColumnValue(PimItemTagRelation::rightColumn(), insTags);
507  qb2.setIdentificationColumn(QString());
508  if (!qb2.exec()) {
509  qCWarning(AKONADISERVER_LOG) << "Failed to add tags" << addedTags << "to Items";
510  return false;
511  }
512  }
513 
514  if (!silent && (!addedTags.empty() || !removedTags.empty())) {
515  notificationCollector()->itemsTagsChanged(items, addedTags, removedTags);
516  }
517 
518  setBoolPtr(tagsChanged, (addedTags != removedTags));
519 
520  return true;
521 }
522 
523 bool DataStore::doAppendItemsTag(const PimItem::List &items, const Tag &tag, const QSet<Entity::Id> &existing, const Collection &col, bool silent)
524 {
525  QVariantList tagIds;
526  QVariantList appendIds;
527  PimItem::List appendItems;
528  for (const PimItem &item : items) {
529  if (existing.contains(item.id())) {
530  continue;
531  }
532 
533  tagIds << tag.id();
534  appendIds << item.id();
535  appendItems << item;
536  }
537 
538  if (appendItems.isEmpty()) {
539  return true; // all items have the desired tags already
540  }
541 
542  QueryBuilder qb2(PimItemTagRelation::tableName(), QueryBuilder::Insert);
543  qb2.setColumnValue(PimItemTagRelation::leftColumn(), appendIds);
544  qb2.setColumnValue(PimItemTagRelation::rightColumn(), tagIds);
545  qb2.setIdentificationColumn(QString());
546  if (!qb2.exec()) {
547  qCWarning(AKONADISERVER_LOG) << "Failed to append tag" << tag << "to Items" << appendItems;
548  return false;
549  }
550 
551  if (!silent) {
552  notificationCollector()->itemsTagsChanged(appendItems, {tag.id()}, {}, col);
553  }
554 
555  return true;
556 }
557 
558 bool DataStore::appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool checkIfExists, const Collection &col, bool silent)
559 {
560  QVariantList itemsIds;
561  itemsIds.reserve(items.count());
562  for (const PimItem &item : items) {
563  itemsIds.append(item.id());
564  }
565 
566  setBoolPtr(tagsChanged, false);
567 
568  for (const Tag &tag : tags) {
569  QSet<PimItem::Id> existing;
570  if (checkIfExists) {
571  QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Select);
572  Query::Condition cond;
573  cond.addValueCondition(PimItemTagRelation::rightColumn(), Query::Equals, tag.id());
574  cond.addValueCondition(PimItemTagRelation::leftColumn(), Query::In, itemsIds);
575  qb.addColumn(PimItemTagRelation::leftColumn());
576  qb.addCondition(cond);
577 
578  if (!qb.exec()) {
579  qCWarning(AKONADISERVER_LOG) << "Failed to retrieve existing tag" << tag << "for Items" << itemsIds;
580  return false;
581  }
582 
583  QSqlQuery query = qb.query();
584  if (query.driver()->hasFeature(QSqlDriver::QuerySize)) {
585  if (query.size() == items.count()) {
586  continue;
587  }
588  setBoolPtr(tagsChanged, true);
589  }
590 
591  while (query.next()) {
592  existing << query.value(0).value<PimItem::Id>();
593  }
594  if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) {
595  if (existing.size() != items.count()) {
596  setBoolPtr(tagsChanged, true);
597  }
598  }
599  query.finish();
600  }
601 
602  if (!doAppendItemsTag(items, tag, existing, col, silent)) {
603  return false;
604  }
605  }
606 
607  return true;
608 }
609 
610 bool DataStore::removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent)
611 {
612  QSet<qint64> removedTags;
613  QVariantList itemsIds;
614  QVariantList tagsIds;
615 
616  setBoolPtr(tagsChanged, false);
617  itemsIds.reserve(items.count());
618 
619  for (const PimItem &item : items) {
620  itemsIds << item.id();
621  for (int i = 0; i < tags.count(); ++i) {
622  const qint64 tagId = tags[i].id();
623  if (!removedTags.contains(tagId)) {
624  tagsIds << tagId;
625  removedTags << tagId;
626  }
627  }
628  }
629 
630  // Delete all given tags from all given items in one go
631  QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Delete);
632  Query::Condition cond(Query::And);
633  cond.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::In, tagsIds);
634  cond.addValueCondition(PimItemTagRelation::leftFullColumnName(), Query::In, itemsIds);
635  qb.addCondition(cond);
636  if (!qb.exec()) {
637  qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << tagsIds << "from Items" << itemsIds;
638  return false;
639  }
640 
641  if (qb.query().numRowsAffected() != 0) {
642  setBoolPtr(tagsChanged, true);
643  if (!silent) {
644  notificationCollector()->itemsTagsChanged(items, QSet<qint64>(), removedTags);
645  }
646  }
647 
648  return true;
649 }
650 
651 bool DataStore::removeTags(const Tag::List &tags, bool silent)
652 {
653  // Currently the "silent" argument is only for API symmetry
654  Q_UNUSED(silent)
655 
656  QVariantList removedTagsIds;
657  QSet<qint64> removedTags;
658  removedTagsIds.reserve(tags.count());
659  removedTags.reserve(tags.count());
660  for (const Tag &tag : tags) {
661  removedTagsIds << tag.id();
662  removedTags << tag.id();
663  }
664 
665  // Get all PIM items that we will untag
666  SelectQueryBuilder<PimItem> itemsQuery;
667  itemsQuery.addJoin(QueryBuilder::LeftJoin, PimItemTagRelation::tableName(), PimItemTagRelation::leftFullColumnName(), PimItem::idFullColumnName());
668  itemsQuery.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::In, removedTagsIds);
669 
670  if (!itemsQuery.exec()) {
671  qCWarning(AKONADISERVER_LOG) << "Removing tags failed: failed to query Items for given tags" << removedTagsIds;
672  return false;
673  }
674  const PimItem::List items = itemsQuery.result();
675 
676  if (!items.isEmpty()) {
677  notificationCollector()->itemsTagsChanged(items, QSet<qint64>(), removedTags);
678  }
679 
680  for (const Tag &tag : tags) {
681  // Emit special tagRemoved notification for each resource that owns the tag
682  QueryBuilder qb(TagRemoteIdResourceRelation::tableName(), QueryBuilder::Select);
683  qb.addColumn(TagRemoteIdResourceRelation::remoteIdFullColumnName());
684  qb.addJoin(QueryBuilder::InnerJoin, Resource::tableName(), TagRemoteIdResourceRelation::resourceIdFullColumnName(), Resource::idFullColumnName());
685  qb.addColumn(Resource::nameFullColumnName());
686  qb.addValueCondition(TagRemoteIdResourceRelation::tagIdFullColumnName(), Query::Equals, tag.id());
687  if (!qb.exec()) {
688  qCWarning(AKONADISERVER_LOG) << "Removing tags failed: failed to retrieve RIDs for tag" << tag.id();
689  return false;
690  }
691 
692  // Emit specialized notifications for each resource
693  QSqlQuery query = qb.query();
694  while (query.next()) {
695  const QString rid = query.value(0).toString();
696  const QByteArray resource = query.value(1).toByteArray();
697 
698  notificationCollector()->tagRemoved(tag, resource, rid);
699  }
700  query.finish();
701 
702  // And one for clients - without RID
704  }
705 
706  // Just remove the tags, table constraints will take care of the rest
707  QueryBuilder qb(Tag::tableName(), QueryBuilder::Delete);
708  qb.addValueCondition(Tag::idColumn(), Query::In, removedTagsIds);
709  if (!qb.exec()) {
710  qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << removedTagsIds;
711  return false;
712  }
713 
714  return true;
715 }
716 
717 /* --- ItemParts ----------------------------------------------------- */
718 
719 bool DataStore::removeItemParts(const PimItem &item, const QSet<QByteArray> &parts)
720 {
722  qb.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName());
723  qb.addValueCondition(Part::pimItemIdFullColumnName(), Query::Equals, item.id());
725 
726  if (!qb.exec()) {
727  qCWarning(AKONADISERVER_LOG) << "Removing item parts failed: failed to query parts" << parts << "from Item " << item.id();
728  return false;
729  }
730 
731  const Part::List existingParts = qb.result();
732  for (Part part : std::as_const(existingParts)) {
733  if (!PartHelper::remove(&part)) {
734  qCWarning(AKONADISERVER_LOG) << "Failed to remove part" << part.id() << "(" << part.partType().ns() << ":" << part.partType().name()
735  << ") from Item" << item.id();
736  return false;
737  }
738  }
739 
740  notificationCollector()->itemChanged(item, parts);
741  return true;
742 }
743 
744 bool DataStore::invalidateItemCache(const PimItem &item)
745 {
746  // find all payload item parts
748  qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), PimItem::idFullColumnName(), Part::pimItemIdFullColumnName());
749  qb.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName());
750  qb.addValueCondition(Part::pimItemIdFullColumnName(), Query::Equals, item.id());
751  qb.addValueCondition(Part::dataFullColumnName(), Query::IsNot, QVariant());
752  qb.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QLatin1String("PLD"));
753  qb.addValueCondition(PimItem::dirtyFullColumnName(), Query::Equals, false);
754 
755  if (!qb.exec()) {
756  qCWarning(AKONADISERVER_LOG) << "Failed to invalidate cache for Item" << item.id();
757  return false;
758  }
759 
760  const Part::List parts = qb.result();
761  // clear data field
762  for (Part part : parts) {
763  if (!PartHelper::truncate(part)) {
764  qCWarning(AKONADISERVER_LOG) << "Failed to truncate payload part" << part.id() << "(" << part.partType().ns() << ":" << part.partType().name()
765  << ") of Item" << item.id();
766  return false;
767  }
768  }
769 
770  return true;
771 }
772 
773 /* --- Collection ------------------------------------------------------ */
774 bool DataStore::appendCollection(Collection &collection, const QStringList &mimeTypes, const QMap<QByteArray, QByteArray> &attributes)
775 {
776  // no need to check for already existing collection with the same name,
777  // a unique index on parent + name prevents that in the database
778  if (!collection.insert()) {
779  qCWarning(AKONADISERVER_LOG) << "Failed to append Collection" << collection.name() << "in resource" << collection.resource().name();
780  return false;
781  }
782 
783  if (!appendMimeTypeForCollection(collection.id(), mimeTypes)) {
784  qCWarning(AKONADISERVER_LOG) << "Failed to append mimetypes" << mimeTypes << "to new collection" << collection.name() << "(ID" << collection.id()
785  << ") in resource" << collection.resource().name();
786  return false;
787  }
788 
789  for (auto it = attributes.cbegin(), end = attributes.cend(); it != end; ++it) {
790  if (!addCollectionAttribute(collection, it.key(), it.value(), true)) {
791  qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << it.key() << "to new collection" << collection.name() << "(ID" << collection.id()
792  << ") in resource" << collection.resource().name();
793  return false;
794  }
795  }
796 
797  notificationCollector()->collectionAdded(collection);
798  return true;
799 }
800 
802 {
803  if (!s_hasForeignKeyConstraints) {
804  return cleanupCollection_slow(collection);
805  }
806 
807  // db will do most of the work for us, we just deal with notifications and external payload parts here
808  Q_ASSERT(s_hasForeignKeyConstraints);
809 
810  // collect item deletion notifications
811  const PimItem::List items = collection.items();
812  const QByteArray resource = collection.resource().name().toLatin1();
813 
814  // generate the notification before actually removing the data
815  // TODO: we should try to get rid of this, requires client side changes to resources and Monitor though
816  notificationCollector()->itemsRemoved(items, collection, resource);
817 
818  // remove all external payload parts
819  QueryBuilder qb(Part::tableName(), QueryBuilder::Select);
820  qb.addColumn(Part::dataFullColumnName());
821  qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), Part::pimItemIdFullColumnName(), PimItem::idFullColumnName());
822  qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName());
823  qb.addValueCondition(Collection::idFullColumnName(), Query::Equals, collection.id());
824  qb.addValueCondition(Part::storageFullColumnName(), Query::Equals, Part::External);
825  qb.addValueCondition(Part::dataFullColumnName(), Query::IsNot, QVariant());
826  if (!qb.exec()) {
827  qCWarning(AKONADISERVER_LOG) << "Failed to cleanup collection" << collection.name() << "(ID" << collection.id() << "):"
828  << "Failed to query existing payload parts";
829  return false;
830  }
831 
832  try {
833  while (qb.query().next()) {
834  ExternalPartStorage::self()->removePartFile(ExternalPartStorage::resolveAbsolutePath(qb.query().value(0).toByteArray()));
835  }
836  } catch (const PartHelperException &e) {
837  qb.query().finish();
838  qCWarning(AKONADISERVER_LOG) << "PartHelperException while cleaning up collection" << collection.name() << "(ID" << collection.id() << "):" << e.what();
839  return false;
840  }
841  qb.query().finish();
842 
843  // delete the collection itself, referential actions will do the rest
845  return collection.remove();
846 }
847 
849 {
850  Q_ASSERT(!s_hasForeignKeyConstraints);
851 
852  // delete the content
853  const PimItem::List items = collection.items();
854  const QByteArray resource = collection.resource().name().toLatin1();
855  notificationCollector()->itemsRemoved(items, collection, resource);
856 
857  for (const PimItem &item : items) {
858  if (!item.clearFlags()) { // TODO: move out of loop and use only a single query
859  qCWarning(AKONADISERVER_LOG) << "Slow cleanup of collection" << collection.name() << "(ID" << collection.id() << ")"
860  << "failed: error clearing items flags";
861  return false;
862  }
863  if (!PartHelper::remove(Part::pimItemIdColumn(), item.id())) { // TODO: reduce to single query
864  qCWarning(AKONADISERVER_LOG) << "Slow cleanup of collection" << collection.name() << "(ID" << collection.id() << ")"
865  << "failed: error clearing item payload parts";
866 
867  return false;
868  }
869 
870  if (!PimItem::remove(PimItem::idColumn(), item.id())) { // TODO: move into single querya
871  qCWarning(AKONADISERVER_LOG) << "Slow cleanup of collection" << collection.name() << "(ID" << collection.id() << ")"
872  << "failed: error clearing items";
873  return false;
874  }
875 
876  if (!Entity::clearRelation<CollectionPimItemRelation>(item.id(), Entity::Right)) { // TODO: move into single query
877  qCWarning(AKONADISERVER_LOG) << "Slow cleanup of collection" << collection.name() << "(ID" << collection.id() << ")"
878  << "failed: error clearing linked items";
879  return false;
880  }
881  }
882 
883  // delete collection mimetypes
884  collection.clearMimeTypes();
885  Collection::clearPimItems(collection.id());
886 
887  // delete attributes
888  const CollectionAttribute::List attrs = collection.attributes();
889  for (CollectionAttribute attr : attrs) {
890  if (!attr.remove()) {
891  qCWarning(AKONADISERVER_LOG) << "Slow cleanup of collection" << collection.name() << "(ID" << collection.id() << ")"
892  << "failed: error clearing attribute" << attr.type();
893  return false;
894  }
895  }
896 
897  // delete the collection itself
899  return collection.remove();
900 }
901 
902 static bool recursiveSetResourceId(const Collection &collection, qint64 resourceId)
903 {
904  Transaction transaction(DataStore::self(), QStringLiteral("RECURSIVE SET RESOURCEID"));
905 
906  QueryBuilder qb(Collection::tableName(), QueryBuilder::Update);
907  qb.addValueCondition(Collection::parentIdColumn(), Query::Equals, collection.id());
908  qb.setColumnValue(Collection::resourceIdColumn(), resourceId);
909  qb.setColumnValue(Collection::remoteIdColumn(), QVariant());
910  qb.setColumnValue(Collection::remoteRevisionColumn(), QVariant());
911  if (!qb.exec()) {
912  qCWarning(AKONADISERVER_LOG) << "Failed to set resource ID" << resourceId << "to collection" << collection.name() << "(ID" << collection.id() << ")";
913  return false;
914  }
915 
916  // this is a cross-resource move, so also reset any resource-specific data (RID, RREV, etc)
917  // as well as mark the items dirty to prevent cache purging before they have been written back
918  qb = QueryBuilder(PimItem::tableName(), QueryBuilder::Update);
919  qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, collection.id());
920  qb.setColumnValue(PimItem::remoteIdColumn(), QVariant());
921  qb.setColumnValue(PimItem::remoteRevisionColumn(), QVariant());
923  qb.setColumnValue(PimItem::datetimeColumn(), now);
924  qb.setColumnValue(PimItem::atimeColumn(), now);
925  qb.setColumnValue(PimItem::dirtyColumn(), true);
926  if (!qb.exec()) {
927  qCWarning(AKONADISERVER_LOG) << "Failed reset RID/RREV for PimItems in Collection" << collection.name() << "(ID" << collection.id() << ")";
928  return false;
929  }
930 
931  transaction.commit();
932 
933  const auto children = collection.children();
934  for (const Collection &col : children) {
935  if (!recursiveSetResourceId(col, resourceId)) {
936  return false;
937  }
938  }
939  return true;
940 }
941 
942 bool DataStore::moveCollection(Collection &collection, const Collection &newParent)
943 {
944  if (collection.parentId() == newParent.id()) {
945  return true;
946  }
947 
948  if (!m_dbOpened) {
949  return false;
950  }
951 
952  if (!newParent.isValid()) {
953  qCWarning(AKONADISERVER_LOG) << "Failed to move collection" << collection.name() << "(ID" << collection.id() << "): invalid destination";
954  return false;
955  }
956 
957  const QByteArray oldResource = collection.resource().name().toLatin1();
958 
959  int resourceId = collection.resourceId();
960  const Collection source = collection.parent();
961  if (newParent.id() > 0) { // not root
962  resourceId = newParent.resourceId();
963  }
964  if (!CollectionQueryHelper::canBeMovedTo(collection, newParent)) {
965  return false;
966  }
967 
968  collection.setParentId(newParent.id());
969  if (collection.resourceId() != resourceId) {
970  collection.setResourceId(resourceId);
971  collection.setRemoteId(QString());
972  collection.setRemoteRevision(QString());
973  if (!recursiveSetResourceId(collection, resourceId)) {
974  return false;
975  }
976  }
977 
978  if (!collection.update()) {
979  qCWarning(AKONADISERVER_LOG) << "Failed to move Collection" << collection.name() << "(ID" << collection.id() << ")"
980  << "into Collection" << collection.name() << "(ID" << collection.id() << ")";
981  return false;
982  }
983 
984  notificationCollector()->collectionMoved(collection, source, oldResource, newParent.resource().name().toLatin1());
985  return true;
986 }
987 
988 bool DataStore::appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes)
989 {
990  if (mimeTypes.isEmpty()) {
991  return true;
992  }
993 
994  for (const QString &mimeType : mimeTypes) {
995  const auto &mt = MimeType::retrieveByNameOrCreate(mimeType);
996  if (!mt.isValid()) {
997  return false;
998  }
999  if (!Collection::addMimeType(collectionId, mt.id())) {
1000  qCWarning(AKONADISERVER_LOG) << "Failed to append mimetype" << mt.name() << "to Collection" << collectionId;
1001  return false;
1002  }
1003  }
1004 
1005  return true;
1006 }
1007 
1009 {
1010  if (!col.cachePolicyInherit()) {
1011  return;
1012  }
1013 
1014  Collection parent = col;
1015  while (parent.parentId() != 0) {
1016  parent = parent.parent();
1017  if (!parent.cachePolicyInherit()) {
1018  col.setCachePolicyCheckInterval(parent.cachePolicyCheckInterval());
1019  col.setCachePolicyCacheTimeout(parent.cachePolicyCacheTimeout());
1020  col.setCachePolicySyncOnDemand(parent.cachePolicySyncOnDemand());
1021  col.setCachePolicyLocalParts(parent.cachePolicyLocalParts());
1022  return;
1023  }
1024  }
1025 
1026  // ### system default
1027  col.setCachePolicyCheckInterval(-1);
1028  col.setCachePolicyCacheTimeout(-1);
1029  col.setCachePolicySyncOnDemand(false);
1030  col.setCachePolicyLocalParts(QStringLiteral("ALL"));
1031 }
1032 
1034 {
1036  qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), Collection::idFullColumnName(), CollectionPimItemRelation::leftFullColumnName());
1037  qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::Equals, item.id());
1038 
1039  if (!qb.exec()) {
1040  qCWarning(AKONADISERVER_LOG) << "Failed to query virtual collections which PimItem" << item.id() << "belongs into";
1041  return QVector<Collection>();
1042  }
1043 
1044  return qb.result();
1045 }
1046 
1048 {
1049  QueryBuilder qb(CollectionPimItemRelation::tableName(), QueryBuilder::Select);
1050  qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), Collection::idFullColumnName(), CollectionPimItemRelation::leftFullColumnName());
1051  qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), PimItem::idFullColumnName(), CollectionPimItemRelation::rightFullColumnName());
1052  qb.addColumn(Collection::idFullColumnName());
1053  qb.addColumns(QStringList() << PimItem::idFullColumnName() << PimItem::remoteIdFullColumnName() << PimItem::remoteRevisionFullColumnName()
1054  << PimItem::mimeTypeIdFullColumnName());
1055  qb.addSortColumn(Collection::idFullColumnName(), Query::Ascending);
1056 
1057  if (items.count() == 1) {
1058  qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::Equals, items.first().id());
1059  } else {
1060  QVariantList ids;
1061  ids.reserve(items.count());
1062  for (const PimItem &item : items) {
1063  ids << item.id();
1064  }
1065  qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::In, ids);
1066  }
1067 
1068  if (!qb.exec()) {
1069  qCWarning(AKONADISERVER_LOG) << "Failed to query virtual Collections which PimItems" << items << "belong into";
1071  }
1072 
1073  QSqlQuery query = qb.query();
1075  query.next();
1076  while (query.isValid()) {
1077  const qlonglong collectionId = query.value(0).toLongLong();
1078  QList<PimItem> &pimItems = map[collectionId];
1079  do {
1080  PimItem item;
1081  item.setId(query.value(1).toLongLong());
1082  item.setRemoteId(query.value(2).toString());
1083  item.setRemoteRevision(query.value(3).toString());
1084  item.setMimeTypeId(query.value(4).toLongLong());
1085  pimItems << item;
1086  } while (query.next() && query.value(0).toLongLong() == collectionId);
1087  }
1088  query.finish();
1089 
1090  return map;
1091 }
1092 
1093 /* --- PimItem ------------------------------------------------------- */
1094 bool DataStore::appendPimItem(QVector<Part> &parts,
1095  const QVector<Flag> &flags,
1096  const MimeType &mimetype,
1097  const Collection &collection,
1098  const QDateTime &dateTime,
1099  const QString &remote_id,
1100  const QString &remoteRevision,
1101  const QString &gid,
1102  PimItem &pimItem)
1103 {
1104  pimItem.setMimeTypeId(mimetype.id());
1105  pimItem.setCollectionId(collection.id());
1106  if (dateTime.isValid()) {
1107  pimItem.setDatetime(dateTime);
1108  }
1109  if (remote_id.isEmpty()) {
1110  // from application
1111  pimItem.setDirty(true);
1112  } else {
1113  // from resource
1114  pimItem.setRemoteId(remote_id);
1115  pimItem.setDirty(false);
1116  }
1117  pimItem.setRemoteRevision(remoteRevision);
1118  pimItem.setGid(gid);
1119  pimItem.setAtime(QDateTime::currentDateTimeUtc());
1120 
1121  if (!pimItem.insert()) {
1122  qCWarning(AKONADISERVER_LOG) << "Failed to append new PimItem into Collection" << collection.name() << "(ID" << collection.id() << ")";
1123  return false;
1124  }
1125 
1126  // insert every part
1127  if (!parts.isEmpty()) {
1128  // don't use foreach, the caller depends on knowing the part has changed, see the Append handler
1129  for (QVector<Part>::iterator it = parts.begin(); it != parts.end(); ++it) {
1130  (*it).setPimItemId(pimItem.id());
1131  if ((*it).datasize() < (*it).data().size()) {
1132  (*it).setDatasize((*it).data().size());
1133  }
1134 
1135  // qCDebug(AKONADISERVER_LOG) << "Insert from DataStore::appendPimItem";
1136  if (!PartHelper::insert(&(*it))) {
1137  qCWarning(AKONADISERVER_LOG) << "Failed to add part" << it->partType().name() << "to new PimItem" << pimItem.id();
1138  return false;
1139  }
1140  }
1141  }
1142 
1143  bool seen = false;
1144  for (const Flag &flag : flags) {
1145  seen |= (flag.name() == QLatin1String(AKONADI_FLAG_SEEN) || flag.name() == QLatin1String(AKONADI_FLAG_IGNORED));
1146  if (!pimItem.addFlag(flag)) {
1147  qCWarning(AKONADISERVER_LOG) << "Failed to add flag" << flag.name() << "to new PimItem" << pimItem.id();
1148  return false;
1149  }
1150  }
1151 
1152  // qCDebug(AKONADISERVER_LOG) << "appendPimItem: " << pimItem;
1153 
1154  notificationCollector()->itemAdded(pimItem, seen, collection);
1155  return true;
1156 }
1157 
1158 bool DataStore::unhidePimItem(PimItem &pimItem)
1159 {
1160  if (!m_dbOpened) {
1161  return false;
1162  }
1163 
1164  qCDebug(AKONADISERVER_LOG) << "DataStore::unhidePimItem(" << pimItem << ")";
1165 
1166  // FIXME: This is inefficient. Using a bit on the PimItemTable record would probably be some orders of magnitude faster...
1167  return removeItemParts(pimItem, {AKONADI_ATTRIBUTE_HIDDEN});
1168 }
1169 
1171 {
1172  if (!m_dbOpened) {
1173  return false;
1174  }
1175 
1176  qCDebug(AKONADISERVER_LOG) << "DataStore::unhideAllPimItems()";
1177 
1178  try {
1179  return PartHelper::remove(Part::partTypeIdFullColumnName(), PartTypeHelper::fromFqName(QStringLiteral("ATR"), QStringLiteral("HIDDEN")).id());
1180  } catch (...) {
1181  } // we can live with this failing
1182 
1183  return false;
1184 }
1185 
1186 bool DataStore::cleanupPimItems(const PimItem::List &items, bool silent)
1187 {
1188  // generate relation removed notifications
1189  if (!silent) {
1190  for (const PimItem &item : items) {
1191  SelectQueryBuilder<Relation> relationQuery;
1192  relationQuery.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, item.id());
1193  relationQuery.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, item.id());
1194  relationQuery.setSubQueryMode(Query::Or);
1195 
1196  if (!relationQuery.exec()) {
1197  throw HandlerException("Failed to obtain relations");
1198  }
1199  const Relation::List relations = relationQuery.result();
1200  for (const Relation &relation : relations) {
1202  }
1203  }
1204 
1205  // generate the notification before actually removing the data
1207  }
1208 
1209  // FIXME: Create a single query to do this
1210  for (const auto &item : items) {
1211  if (!item.clearFlags()) {
1212  qCWarning(AKONADISERVER_LOG) << "Failed to clean up flags from PimItem" << item.id();
1213  return false;
1214  }
1215  if (!PartHelper::remove(Part::pimItemIdColumn(), item.id())) {
1216  qCWarning(AKONADISERVER_LOG) << "Failed to clean up parts from PimItem" << item.id();
1217  return false;
1218  }
1219  if (!PimItem::remove(PimItem::idColumn(), item.id())) {
1220  qCWarning(AKONADISERVER_LOG) << "Failed to remove PimItem" << item.id();
1221  return false;
1222  }
1223 
1224  if (!Entity::clearRelation<CollectionPimItemRelation>(item.id(), Entity::Right)) {
1225  qCWarning(AKONADISERVER_LOG) << "Failed to remove PimItem" << item.id() << "from linked collections";
1226  return false;
1227  }
1228  }
1229 
1230  return true;
1231 }
1232 
1233 bool DataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value, bool silent)
1234 {
1236  qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, col.id());
1237  qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, key);
1238  if (!qb.exec()) {
1239  qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id()
1240  << "): Failed to query existing attribute";
1241  return false;
1242  }
1243 
1244  if (!qb.result().isEmpty()) {
1245  qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id()
1246  << "): Attribute already exists";
1247  return false;
1248  }
1249 
1250  CollectionAttribute attr;
1251  attr.setCollectionId(col.id());
1252  attr.setType(key);
1253  attr.setValue(value);
1254 
1255  if (!attr.insert()) {
1256  qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id() << ")";
1257  return false;
1258  }
1259 
1260  if (!silent) {
1262  }
1263  return true;
1264 }
1265 
1267 {
1269  qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, col.id());
1270  qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, key);
1271  if (!qb.exec()) {
1272  throw HandlerException("Unable to query for collection attribute");
1273  }
1274 
1275  const QVector<CollectionAttribute> result = qb.result();
1276  for (CollectionAttribute attr : result) {
1277  if (!attr.remove()) {
1278  throw HandlerException("Unable to remove collection attribute");
1279  }
1280  }
1281 
1282  if (!result.isEmpty()) {
1284  return true;
1285  }
1286  return false;
1287 }
1288 
1289 void DataStore::debugLastDbError(const char *actionDescription) const
1290 {
1291  qCCritical(AKONADISERVER_LOG) << "Database error:" << actionDescription;
1292  qCCritical(AKONADISERVER_LOG) << " Last driver error:" << m_database.lastError().driverText();
1293  qCCritical(AKONADISERVER_LOG) << " Last database error:" << m_database.lastError().databaseText();
1294 
1295  m_akonadi.tracer().error("DataStore (Database Error)",
1296  QStringLiteral("%1\nDriver said: %2\nDatabase said:%3")
1297  .arg(QString::fromLatin1(actionDescription), m_database.lastError().driverText(), m_database.lastError().databaseText()));
1298 }
1299 
1300 void DataStore::debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const
1301 {
1302  qCCritical(AKONADISERVER_LOG) << "Query error:" << actionDescription;
1303  qCCritical(AKONADISERVER_LOG) << " Last error message:" << query.lastError().text();
1304  qCCritical(AKONADISERVER_LOG) << " Last driver error:" << m_database.lastError().driverText();
1305  qCCritical(AKONADISERVER_LOG) << " Last database error:" << m_database.lastError().databaseText();
1306 
1307  m_akonadi.tracer().error("DataStore (Database Query Error)",
1308  QStringLiteral("%1: %2").arg(QString::fromLatin1(actionDescription), query.lastError().text()));
1309 }
1310 
1311 // static
1312 QString DataStore::dateTimeFromQDateTime(const QDateTime &dateTime)
1313 {
1314  QDateTime utcDateTime = dateTime;
1315  if (utcDateTime.timeSpec() != Qt::UTC) {
1316  utcDateTime = utcDateTime.toUTC();
1317  }
1318  return utcDateTime.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"));
1319 }
1320 
1321 // static
1322 QDateTime DataStore::dateTimeToQDateTime(const QByteArray &dateTime)
1323 {
1324  return QDateTime::fromString(QString::fromLatin1(dateTime), QStringLiteral("yyyy-MM-dd hh:mm:ss"));
1325 }
1326 
1327 bool DataStore::doRollback()
1328 {
1329  QSqlDriver *driver = m_database.driver();
1330  QElapsedTimer timer;
1331  timer.start();
1332  driver->rollbackTransaction();
1333  StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), false, timer.elapsed(), m_database.lastError().text());
1334  if (m_database.lastError().isValid()) {
1335  TRANSACTION_MUTEX_UNLOCK;
1336  debugLastDbError("DataStore::rollbackTransaction");
1337  return false;
1338  }
1339  TRANSACTION_MUTEX_UNLOCK;
1340  return true;
1341 }
1342 
1343 void DataStore::transactionKilledByDB()
1344 {
1345  m_transactionKilledByDB = true;
1346  cleanupAfterRollback();
1348 }
1349 
1351 {
1352  if (!m_dbOpened) {
1353  return false;
1354  }
1355 
1356  if (m_transactionLevel == 0 || m_transactionKilledByDB) {
1357  m_transactionKilledByDB = false;
1358  QElapsedTimer timer;
1359  timer.start();
1360  TRANSACTION_MUTEX_LOCK;
1361  if (DbType::type(m_database) == DbType::Sqlite) {
1362  m_database.exec(QStringLiteral("BEGIN IMMEDIATE TRANSACTION"));
1363  StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), name, timer.elapsed(), m_database.lastError().text());
1364  if (m_database.lastError().isValid()) {
1365  debugLastDbError("DataStore::beginTransaction (SQLITE)");
1366  TRANSACTION_MUTEX_UNLOCK;
1367  return false;
1368  }
1369  } else {
1370  m_database.driver()->beginTransaction();
1371  StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), name, timer.elapsed(), m_database.lastError().text());
1372  if (m_database.lastError().isValid()) {
1373  debugLastDbError("DataStore::beginTransaction");
1374  TRANSACTION_MUTEX_UNLOCK;
1375  return false;
1376  }
1377  }
1378 
1379  if (DbType::type(m_database) == DbType::PostgreSQL) {
1380  // Make constraints check deferred in PostgreSQL. Allows for
1381  // INSERT INTO mimetypetable (name) VALUES ('foo') RETURNING id;
1382  // INSERT INTO collectionmimetyperelation (collection_id, mimetype_id) VALUES (x, y)
1383  // where "y" refers to the newly inserted mimetype
1384  m_database.exec(QStringLiteral("SET CONSTRAINTS ALL DEFERRED"));
1385  }
1386  }
1387 
1388  ++m_transactionLevel;
1389 
1390  return true;
1391 }
1392 
1394 {
1395  if (!m_dbOpened) {
1396  return false;
1397  }
1398 
1399  if (m_transactionLevel == 0) {
1400  qCWarning(AKONADISERVER_LOG) << "DataStore::rollbackTransaction(): No transaction in progress!";
1401  return false;
1402  }
1403 
1404  --m_transactionLevel;
1405 
1406  if (m_transactionLevel == 0 && !m_transactionKilledByDB) {
1407  doRollback();
1408  cleanupAfterRollback();
1410  }
1411 
1412  return true;
1413 }
1414 
1416 {
1417  if (!m_dbOpened) {
1418  return false;
1419  }
1420 
1421  if (m_transactionLevel == 0) {
1422  qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): No transaction in progress!";
1423  return false;
1424  }
1425 
1426  if (m_transactionLevel == 1) {
1427  if (m_transactionKilledByDB) {
1428  qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): Cannot commit, transaction was killed by mysql deadlock handling!";
1429  return false;
1430  }
1431  QSqlDriver *driver = m_database.driver();
1432  QElapsedTimer timer;
1433  timer.start();
1434  driver->commitTransaction();
1435  StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), true, timer.elapsed(), m_database.lastError().text());
1436  if (m_database.lastError().isValid()) {
1437  debugLastDbError("DataStore::commitTransaction");
1439  return false;
1440  } else {
1441  TRANSACTION_MUTEX_UNLOCK;
1442  m_transactionLevel--;
1444  }
1445  } else {
1446  m_transactionLevel--;
1447  }
1448  return true;
1449 }
1450 
1452 {
1453  return m_transactionLevel > 0;
1454 }
1455 
1456 void DataStore::sendKeepAliveQuery()
1457 {
1458  if (m_database.isOpen()) {
1459  QSqlQuery query(m_database);
1460  query.exec(QStringLiteral("SELECT 1"));
1461  }
1462 }
1463 
1464 void DataStore::cleanupAfterRollback()
1465 {
1466  MimeType::invalidateCompleteCache();
1467  Flag::invalidateCompleteCache();
1468  Resource::invalidateCompleteCache();
1469  Collection::invalidateCompleteCache();
1470  PartType::invalidateCompleteCache();
1471  m_akonadi.collectionStatistics().expireCache();
1473 }
Updates the database schema.
Definition: dbupdater.h:45
NotificationCollector * notificationCollector()
Returns the notification collector of this DataStore object.
Definition: datastore.cpp:206
Query::Condition conditionFromFqNames(const QStringList &fqNames)
Returns a query condition that matches the given part type list.
This class handles all the database access.
Definition: datastore.h:94
QVariant value(int index) const const
virtual bool commitTransaction()
bool isEmpty() const const
Helper class for DataStore transaction handling.
Definition: transaction.h:22
virtual bool cleanupCollection_slow(Collection &collection)
same as the above but for database backends without working referential actions on foreign keys
Definition: datastore.cpp:848
virtual void open()
Opens the database connection.
Definition: datastore.cpp:101
QString number(int n, int base)
static DataStore * self()
Per thread singleton.
Definition: datastore.cpp:215
void setId(Id identifier)
Sets the unique identifier of the collection.
Definition: collection.cpp:91
Part of the DataStore, collects change notifications and emits them after the current transaction has...
Attribute::List attributes() const
Returns a list of all attributes of the collection.
Definition: collection.cpp:166
QVector< Collection > virtualCollections(const PimItem &item)
Returns all virtual collections the item is linked to.
Definition: datastore.cpp:1033
Q_EMITQ_EMIT
bool inTransaction() const
Returns true if there is a transaction in progress.
Definition: datastore.cpp:1451
Type type(const QSqlDatabase &db)
Returns the type of the given database object.
Definition: dbtype.cpp:11
QVector::iterator begin()
@ InnerJoin
NOTE: only supported for UPDATE and SELECT queries.
Definition: querybuilder.h:48
void collectionRemoved(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a removed collection.
An Akonadi Tag.
Definition: tag.h:25
QSqlDatabase database()
Returns the QSqlDatabase object.
Definition: datastore.cpp:135
void reserve(int size)
virtual void apply(QSqlDatabase &database)=0
This method applies the configured settings to the QtSql database instance.
Represents a collection of PIM items.
Definition: collection.h:61
PartType fromFqName(const QString &fqName)
Retrieve (or create) PartType for the given fully qualified name.
void removeDatabase(const QString &connectionName)
void close()
Closes the database connection.
Definition: datastore.cpp:143
QDateTime currentDateTimeUtc()
QString databaseText() const const
void itemsRemoved(const PimItem::List &items, const Collection &collection=Collection(), const QByteArray &resource=QByteArray())
Notify about removed items.
QByteArray toLatin1() const const
bool contains(const QString &connectionName)
void itemsTagsChanged(const PimItem::List &items, const QSet< qint64 > &addedTags, const QSet< qint64 > &removedTags, const Collection &collection=Collection(), const QByteArray &resource=QByteArray())
Notify about changed items tags.
An Akonadi Relation.
Definition: relation.h:39
QByteArray toByteArray() const const
bool insert(Part *part, qint64 *insertId=nullptr)
Adds a new part to the database and if necessary to the filesystem.
Definition: parthelper.cpp:58
bool truncate(Part &part)
Truncate the payload of part and update filesystem/database accordingly.
Definition: parthelper.cpp:152
Definition: item.h:32
bool exists() const const
void setColumnValue(const QString &column, const QVariant &value)
Sets a column to the given value (only valid for INSERT and UPDATE queries).
@ LeftJoin
NOTE: only supported for SELECT queries.
Definition: querybuilder.h:50
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual bool beginTransaction(const QString &name)
Begins a transaction.
Definition: datastore.cpp:1350
QThread * thread() const const
QMap::const_iterator cbegin() const const
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
virtual bool unhidePimItem(PimItem &pimItem)
Unhides the specified PimItem.
Definition: datastore.cpp:1158
void addJoin(JoinType joinType, const QString &table, const Query::Condition &condition)
Join a table to the query.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
static DbConfig * configuredDatabase()
Returns the DbConfig instance for the database the user has configured.
Definition: dbconfig.cpp:73
int size() const const
bool remove(Part *part)
Deletes part from the database and also removes existing filesystem data if needed.
Definition: parthelper.cpp:90
void start(int msec)
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
static DbInitializer::Ptr createInstance(const QSqlDatabase &database, Schema *schema=nullptr)
Returns an initializer instance for a given backend.
bool isValid() const const
virtual bool rollbackTransaction()
Reverts all changes within the current transaction.
Definition: datastore.cpp:1393
QMap::const_iterator cend() const const
bool isValid() const const
virtual bool beginTransaction()
void timeout()
bool isEmpty() const const
void collectionMoved(const Collection &collection, const Collection &source, const QByteArray &resource=QByteArray(), const QByteArray &destResource=QByteArray())
Notify about a moved collection.
virtual void activeCachePolicy(Collection &col)
Determines the active cache policy for this Collection.
Definition: datastore.cpp:1008
void setRemoteId(const QString &id)
Sets the remote id of the collection.
Definition: collection.cpp:101
QCoreApplication * instance()
QVector< T > result()
Returns the result of this SELECT query.
virtual bool cleanupPimItems(const PimItem::List &items, bool silent=false)
Removes the pim item and all referenced data ( e.g.
Definition: datastore.cpp:1186
QUuid createUuid()
void transactionRolledBack()
Emitted if a transaction has been aborted.
QThread * currentThread()
void finish()
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
bool contains(const T &value) const const
bool next()
void tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId)
Notify about a removed tag.
void addColumn(const QString &col)
Adds the given column to a select query.
bool isEmpty() const const
virtual bool commitTransaction()
Commits all changes within the current transaction and emits all collected notification signals.
Definition: datastore.cpp:1415
QDateTime fromString(const QString &string, Qt::DateFormat format)
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 transactionCommitted()
Emitted if a transaction has been successfully committed.
qint64 elapsed() const const
void collectionChanged(const Collection &collection, const QList< QByteArray > &changes, const QByteArray &resource=QByteArray())
Notify about a changed collection.
QString driverText() const const
QString toString() const const
bool contains(const T &value) const const
DataStore(AkonadiServer &akonadi)
Creates a new DataStore object and opens it.
Definition: datastore.cpp:80
QDateTime toUTC() const const
~DataStore() override
Closes the database connection and destroys the DataStore object.
Definition: datastore.cpp:96
void collectionAdded(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a added collection.
bool exec()
Executes the query, returns true on success.
bool isOpen() const const
QVector::iterator end()
QString text() const const
bool canBeMovedTo(const Collection &collection, const Collection &parent)
Checks if a collection could be moved from its current parent into the given one.
virtual bool moveCollection(Collection &collection, const Collection &newParent)
moves the collection collection to newParent.
Definition: datastore.cpp:942
bool empty() const const
Helper class for creating and executing database SELECT queries.
virtual bool cleanupCollection(Collection &collection)
removes the given collection and all its content
Definition: datastore.cpp:801
void stop()
Id id() const
Returns the unique identifier of the tag.
Definition: tag.cpp:139
QString fromLatin1(const char *str, int size)
void itemAdded(const PimItem &item, bool seen, const Collection &collection=Collection(), const QByteArray &resource=QByteArray())
Notify about an added item.
Qt::TimeSpec timeSpec() const const
virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key)
Removes the given collection attribute for col.
Definition: datastore.cpp:1266
void addColumns(const QStringList &cols)
Adds the given columns to a select query.
bool isValid() const const
QStringList mimeTypes(Mode mode=Writing)
QSqlDriver * driver() const const
bool run()
Starts the update process.
Definition: dbupdater.cpp:43
QSet::iterator insert(const T &value)
int count(const T &value) const const
void setSubQueryMode(Query::LogicOperator op, ConditionType type=WhereCondition)
Define how WHERE or HAVING conditions are combined.
Represents a WHERE condition tree.
Definition: query.h:61
void itemsFlagsChanged(const PimItem::List &items, const QSet< QByteArray > &addedFlags, const QSet< QByteArray > &removedFlags, const Collection &collection=Collection(), const QByteArray &resource=QByteArray())
Notify about changed items flags Provide as many parameters as you have at hand currently,...
QSqlQuery & query()
Returns the query, only valid after exec().
static bool hasDataStore()
Returns whether per thread DataStore has been created.
Definition: datastore.cpp:223
void addCondition(const Query::Condition &condition, ConditionType type=WhereCondition)
Add a WHERE condition.
QString toString(Qt::DateFormat format) const const
void setInterval(int msec)
QSqlError lastError() const const
virtual bool rollbackTransaction()
QSqlQuery exec(const QString &query) const const
void addSortColumn(const QString &column, Query::SortOrder order=Query::Ascending)
Add sort column.
void objectNameChanged(const QString &objectName)
QFuture< void > map(Sequence &sequence, MapFunctor function)
virtual void initSession(const QSqlDatabase &database)
Do session setup/initialization work on database.
Definition: dbconfig.cpp:142
void itemChanged(const PimItem &item, const QSet< QByteArray > &changedParts, const Collection &collection=Collection(), const QByteArray &resource=QByteArray())
Notify about a changed item.
QString driverName() const const
virtual bool unhideAllPimItems()
Unhides all the items which have the "hidden" flag set.
Definition: datastore.cpp:1170
QObject * parent() const const
void relationRemoved(const Relation &relation)
Notify about a removed relation.
T value(int i) const const
bool isEmpty() const const
void setRemoteRevision(const QString &revision)
Sets the remote revision of the collection.
Definition: collection.cpp:111
Helper class to construct arbitrary SQL queries.
Definition: querybuilder.h:31
QString databaseName() const const
void clear()
Clears all queries from current thread.
Definition: querycache.cpp:107
virtual bool init()
Initializes the database.
Definition: datastore.cpp:170
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:01:05 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.