Akonadi

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

KDE's Doxygen guidelines are available online.