Akonadi

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

KDE's Doxygen guidelines are available online.