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

KDE's Doxygen guidelines are available online.