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;
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);
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{
272 QVariantList insIds;
273 QVariantList insFlags;
274 Query::Condition delConds(Query::Or);
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();
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())) {
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 }
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{
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 QueryBuilder qb2(PimItemFlagRelation::tableName(), QueryBuilder::Insert);
368 qb2.setColumnValue(PimItemFlagRelation::leftColumn(), appendIds);
369 qb2.setColumnValue(PimItemFlagRelation::rightColumn(), flagIds);
370 qb2.setIdentificationColumn(QString());
371 if (!qb2.exec()) {
372 qCWarning(AKONADISERVER_LOG) << "Failed to append flag" << flag.name() << "to Items" << appendIds;
373 return false;
374 }
375
376 if (!silent) {
377 notificationCollector()->itemsFlagsChanged(appendItems, {flag.name().toLatin1()}, {}, col);
378 }
379
380 return true;
381}
382
383bool DataStore::appendItemsFlags(const PimItem::List &items,
384 const QList<Flag> &flags,
385 bool *flagsChanged,
386 bool checkIfExists,
387 const Collection &col,
388 bool silent)
389{
390 QVariantList itemsIds;
391 itemsIds.reserve(items.count());
392 for (const PimItem &item : items) {
393 itemsIds.append(item.id());
394 }
395
396 setBoolPtr(flagsChanged, false);
397
398 for (const Flag &flag : flags) {
400 if (checkIfExists) {
401 QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Select);
403 cond.addValueCondition(PimItemFlagRelation::rightColumn(), Query::Equals, flag.id());
404 cond.addValueCondition(PimItemFlagRelation::leftColumn(), Query::In, itemsIds);
405 qb.addColumn(PimItemFlagRelation::leftColumn());
406 qb.addCondition(cond);
407
408 if (!qb.exec()) {
409 qCWarning(AKONADISERVER_LOG) << "Failed to retrieve existing flags for Items " << itemsIds;
410 return false;
411 }
412
413 QSqlQuery query = qb.query();
414 if (query.driver()->hasFeature(QSqlDriver::QuerySize)) {
415 // The query size feature is not supported by the sqllite driver
416 if (query.size() == items.count()) {
417 continue;
418 }
419 setBoolPtr(flagsChanged, true);
420 }
421
422 while (query.next()) {
423 existing << query.value(0).value<PimItem::Id>();
424 }
425 if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) {
426 if (existing.size() != items.count()) {
427 setBoolPtr(flagsChanged, true);
428 }
429 }
430 query.finish();
431 }
432
433 if (!doAppendItemsFlag(items, flag, existing, col, silent)) {
434 return false;
435 }
436 }
437
438 return true;
439}
440
441bool DataStore::removeItemsFlags(const PimItem::List &items, const QList<Flag> &flags, bool *flagsChanged, const Collection &col_, bool silent)
442{
445 QVariantList itemsIds;
446 QVariantList flagsIds;
447
448 setBoolPtr(flagsChanged, false);
449 itemsIds.reserve(items.count());
450
451 for (const PimItem &item : items) {
452 itemsIds << item.id();
453 if (col.id() == -1) {
454 col.setId(item.collectionId());
455 } else if (col.id() != item.collectionId()) {
456 col.setId(-2);
457 }
458 for (int i = 0; i < flags.count(); ++i) {
459 const QString flagName = flags[i].name();
460 if (!removedFlags.contains(flagName)) {
461 flagsIds << flags[i].id();
463 }
464 }
465 }
466
467 // Delete all given flags from all given items in one go
468 QueryBuilder qb(PimItemFlagRelation::tableName(), QueryBuilder::Delete);
469 Query::Condition cond(Query::And);
470 cond.addValueCondition(PimItemFlagRelation::rightFullColumnName(), Query::In, flagsIds);
471 cond.addValueCondition(PimItemFlagRelation::leftFullColumnName(), Query::In, itemsIds);
472 qb.addCondition(cond);
473 if (!qb.exec()) {
474 qCWarning(AKONADISERVER_LOG) << "Failed to remove flags" << flags << "from Items" << itemsIds;
475 return false;
476 }
477
478 if (qb.query().numRowsAffected() != 0) {
479 setBoolPtr(flagsChanged, true);
480 if (!silent) {
482 for (const auto &remoteFlag : std::as_const(removedFlags)) {
483 removedFlagsBa.insert(remoteFlag.toLatin1());
484 }
486 }
487 }
488
489 return true;
490}
491
492/* --- ItemTags ----------------------------------------------------- */
493
494bool DataStore::setItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent)
495{
498 QVariantList insIds;
499 QVariantList insTags;
500 Query::Condition delConds(Query::Or);
501
502 setBoolPtr(tagsChanged, false);
503
504 for (const PimItem &item : items) {
505 const Tag::List itemTags = item.tags();
506 for (const Tag &tag : itemTags) {
507 if (!tags.contains(tag)) {
508 // Remove tags from items that had it set
509 removedTags << tag.id();
511 cond.addValueCondition(PimItemTagRelation::leftFullColumnName(), Query::Equals, item.id());
512 cond.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::Equals, tag.id());
513 delConds.addCondition(cond);
514 }
515 }
516
517 for (const Tag &tag : tags) {
518 if (!itemTags.contains(tag)) {
519 // Add tags to items that did not have the tag
520 addedTags << tag.id();
521 insIds << item.id();
522 insTags << tag.id();
523 }
524 }
525 }
526
527 if (!removedTags.empty()) {
528 QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Delete);
529 qb.addCondition(delConds);
530 if (!qb.exec()) {
531 qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << removedTags << "from Items";
532 return false;
533 }
534 }
535
536 if (!addedTags.empty()) {
537 QueryBuilder qb2(PimItemTagRelation::tableName(), QueryBuilder::Insert);
538 qb2.setColumnValue(PimItemTagRelation::leftColumn(), insIds);
539 qb2.setColumnValue(PimItemTagRelation::rightColumn(), insTags);
540 qb2.setIdentificationColumn(QString());
541 if (!qb2.exec()) {
542 qCWarning(AKONADISERVER_LOG) << "Failed to add tags" << addedTags << "to Items";
543 return false;
544 }
545 }
546
547 if (!silent && (!addedTags.empty() || !removedTags.empty())) {
549 }
550
551 setBoolPtr(tagsChanged, (addedTags != removedTags));
552
553 return true;
554}
555
556bool DataStore::doAppendItemsTag(const PimItem::List &items, const Tag &tag, const QSet<Entity::Id> &existing, const Collection &col, bool silent)
557{
558 QVariantList tagIds;
559 QVariantList appendIds;
560 PimItem::List appendItems;
561 for (const PimItem &item : items) {
562 if (existing.contains(item.id())) {
563 continue;
564 }
565
566 tagIds << tag.id();
567 appendIds << item.id();
568 appendItems << item;
569 }
570
571 if (appendItems.isEmpty()) {
572 return true; // all items have the desired tags already
573 }
574
575 QueryBuilder qb2(PimItemTagRelation::tableName(), QueryBuilder::Insert);
576 qb2.setColumnValue(PimItemTagRelation::leftColumn(), appendIds);
577 qb2.setColumnValue(PimItemTagRelation::rightColumn(), tagIds);
578 qb2.setIdentificationColumn(QString());
579 if (!qb2.exec()) {
580 qCWarning(AKONADISERVER_LOG) << "Failed to append tag" << tag << "to Items" << appendItems;
581 return false;
582 }
583
584 if (!silent) {
586 }
587
588 return true;
589}
590
591bool DataStore::appendItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool checkIfExists, const Collection &col, bool silent)
592{
593 QVariantList itemsIds;
594 itemsIds.reserve(items.count());
595 for (const PimItem &item : items) {
596 itemsIds.append(item.id());
597 }
598
599 setBoolPtr(tagsChanged, false);
600
601 for (const Tag &tag : tags) {
603 if (checkIfExists) {
604 QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Select);
606 cond.addValueCondition(PimItemTagRelation::rightColumn(), Query::Equals, tag.id());
607 cond.addValueCondition(PimItemTagRelation::leftColumn(), Query::In, itemsIds);
608 qb.addColumn(PimItemTagRelation::leftColumn());
609 qb.addCondition(cond);
610
611 if (!qb.exec()) {
612 qCWarning(AKONADISERVER_LOG) << "Failed to retrieve existing tag" << tag << "for Items" << itemsIds;
613 return false;
614 }
615
616 QSqlQuery query = qb.query();
617 if (query.driver()->hasFeature(QSqlDriver::QuerySize)) {
618 if (query.size() == items.count()) {
619 continue;
620 }
621 setBoolPtr(tagsChanged, true);
622 }
623
624 while (query.next()) {
625 existing << query.value(0).value<PimItem::Id>();
626 }
627 if (!query.driver()->hasFeature(QSqlDriver::QuerySize)) {
628 if (existing.size() != items.count()) {
629 setBoolPtr(tagsChanged, true);
630 }
631 }
632 query.finish();
633 }
634
635 if (!doAppendItemsTag(items, tag, existing, col, silent)) {
636 return false;
637 }
638 }
639
640 return true;
641}
642
643bool DataStore::removeItemsTags(const PimItem::List &items, const Tag::List &tags, bool *tagsChanged, bool silent)
644{
646 QVariantList itemsIds;
647 QVariantList tagsIds;
648
649 setBoolPtr(tagsChanged, false);
650 itemsIds.reserve(items.count());
651
652 for (const PimItem &item : items) {
653 itemsIds << item.id();
654 for (int i = 0; i < tags.count(); ++i) {
655 const qint64 tagId = tags[i].id();
656 if (!removedTags.contains(tagId)) {
657 tagsIds << tagId;
658 removedTags << tagId;
659 }
660 }
661 }
662
663 // Delete all given tags from all given items in one go
664 QueryBuilder qb(PimItemTagRelation::tableName(), QueryBuilder::Delete);
665 Query::Condition cond(Query::And);
666 cond.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::In, tagsIds);
667 cond.addValueCondition(PimItemTagRelation::leftFullColumnName(), Query::In, itemsIds);
668 qb.addCondition(cond);
669 if (!qb.exec()) {
670 qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << tagsIds << "from Items" << itemsIds;
671 return false;
672 }
673
674 if (qb.query().numRowsAffected() != 0) {
675 setBoolPtr(tagsChanged, true);
676 if (!silent) {
678 }
679 }
680
681 return true;
682}
683
684bool DataStore::removeTags(const Tag::List &tags, bool silent)
685{
686 // Currently the "silent" argument is only for API symmetry
687 Q_UNUSED(silent)
688
689 QVariantList removedTagsIds;
691 removedTagsIds.reserve(tags.count());
692 removedTags.reserve(tags.count());
693 for (const Tag &tag : tags) {
694 removedTagsIds << tag.id();
695 removedTags << tag.id();
696 }
697
698 // Get all PIM items that we will untag
700 itemsQuery.addJoin(QueryBuilder::LeftJoin, PimItemTagRelation::tableName(), PimItemTagRelation::leftFullColumnName(), PimItem::idFullColumnName());
701 itemsQuery.addValueCondition(PimItemTagRelation::rightFullColumnName(), Query::In, removedTagsIds);
702
703 if (!itemsQuery.exec()) {
704 qCWarning(AKONADISERVER_LOG) << "Removing tags failed: failed to query Items for given tags" << removedTagsIds;
705 return false;
706 }
707 const PimItem::List items = itemsQuery.result();
708
709 if (!items.isEmpty()) {
711 }
712
713 for (const Tag &tag : tags) {
714 // Emit special tagRemoved notification for each resource that owns the tag
715 QueryBuilder qb(TagRemoteIdResourceRelation::tableName(), QueryBuilder::Select);
716 qb.addColumn(TagRemoteIdResourceRelation::remoteIdFullColumnName());
717 qb.addJoin(QueryBuilder::InnerJoin, Resource::tableName(), TagRemoteIdResourceRelation::resourceIdFullColumnName(), Resource::idFullColumnName());
718 qb.addColumn(Resource::nameFullColumnName());
719 qb.addValueCondition(TagRemoteIdResourceRelation::tagIdFullColumnName(), Query::Equals, tag.id());
720 if (!qb.exec()) {
721 qCWarning(AKONADISERVER_LOG) << "Removing tags failed: failed to retrieve RIDs for tag" << tag.id();
722 return false;
723 }
724
725 // Emit specialized notifications for each resource
726 QSqlQuery query = qb.query();
727 while (query.next()) {
728 const QString rid = query.value(0).toString();
729 const QByteArray resource = query.value(1).toByteArray();
730
731 notificationCollector()->tagRemoved(tag, resource, rid);
732 }
733 query.finish();
734
735 // And one for clients - without RID
737 }
738
739 // Just remove the tags, table constraints will take care of the rest
740 QueryBuilder qb(Tag::tableName(), QueryBuilder::Delete);
741 qb.addValueCondition(Tag::idColumn(), Query::In, removedTagsIds);
742 if (!qb.exec()) {
743 qCWarning(AKONADISERVER_LOG) << "Failed to remove tags" << removedTagsIds;
744 return false;
745 }
746
747 return true;
748}
749
750/* --- ItemParts ----------------------------------------------------- */
751
752bool DataStore::removeItemParts(const PimItem &item, const QSet<QByteArray> &parts)
753{
755 qb.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName());
756 qb.addValueCondition(Part::pimItemIdFullColumnName(), Query::Equals, item.id());
757 qb.addCondition(PartTypeHelper::conditionFromFqNames(parts));
758
759 if (!qb.exec()) {
760 qCWarning(AKONADISERVER_LOG) << "Removing item parts failed: failed to query parts" << parts << "from Item " << item.id();
761 return false;
762 }
763
764 const Part::List existingParts = qb.result();
765 for (Part part : std::as_const(existingParts)) {
766 if (!PartHelper::remove(&part)) {
767 qCWarning(AKONADISERVER_LOG) << "Failed to remove part" << part.id() << "(" << part.partType().ns() << ":" << part.partType().name()
768 << ") from Item" << item.id();
769 return false;
770 }
771 }
772
773 notificationCollector()->itemChanged(item, parts);
774 return true;
775}
776
777bool DataStore::invalidateItemCache(const PimItem &item)
778{
779 // find all payload item parts
781 qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), PimItem::idFullColumnName(), Part::pimItemIdFullColumnName());
782 qb.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName());
783 qb.addValueCondition(Part::pimItemIdFullColumnName(), Query::Equals, item.id());
784 qb.addValueCondition(Part::dataFullColumnName(), Query::IsNot, QVariant());
785 qb.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QLatin1StringView("PLD"));
786 qb.addValueCondition(PimItem::dirtyFullColumnName(), Query::Equals, false);
787
788 if (!qb.exec()) {
789 qCWarning(AKONADISERVER_LOG) << "Failed to invalidate cache for Item" << item.id();
790 return false;
791 }
792
793 const Part::List parts = qb.result();
794 // clear data field
795 for (Part part : parts) {
796 if (!PartHelper::truncate(part)) {
797 qCWarning(AKONADISERVER_LOG) << "Failed to truncate payload part" << part.id() << "(" << part.partType().ns() << ":" << part.partType().name()
798 << ") of Item" << item.id();
799 return false;
800 }
801 }
802
803 return true;
804}
805
806/* --- Collection ------------------------------------------------------ */
807bool DataStore::appendCollection(Collection &collection, const QStringList &mimeTypes, const QMap<QByteArray, QByteArray> &attributes)
808{
809 // no need to check for already existing collection with the same name,
810 // a unique index on parent + name prevents that in the database
811 if (!collection.insert()) {
812 qCWarning(AKONADISERVER_LOG) << "Failed to append Collection" << collection.name() << "in resource" << collection.resource().name();
813 return false;
814 }
815
816 if (!appendMimeTypeForCollection(collection.id(), mimeTypes)) {
817 qCWarning(AKONADISERVER_LOG) << "Failed to append mimetypes" << mimeTypes << "to new collection" << collection.name() << "(ID" << collection.id()
818 << ") in resource" << collection.resource().name();
819 return false;
820 }
821
822 for (auto it = attributes.cbegin(), end = attributes.cend(); it != end; ++it) {
823 if (!addCollectionAttribute(collection, it.key(), it.value(), true)) {
824 qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << it.key() << "to new collection" << collection.name() << "(ID" << collection.id()
825 << ") in resource" << collection.resource().name();
826 return false;
827 }
828 }
829
831 return true;
832}
833
835{
836 // collect item deletion notifications
837 const PimItem::List items = collection.items();
838 const QByteArray resource = collection.resource().name().toLatin1();
839
840 // generate the notification before actually removing the data
841 // TODO: we should try to get rid of this, requires client side changes to resources and Monitor though
842 notificationCollector()->itemsRemoved(items, collection, resource);
843
844 // remove all external payload parts
845 QueryBuilder qb(Part::tableName(), QueryBuilder::Select);
846 qb.addColumn(Part::dataFullColumnName());
847 qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), Part::pimItemIdFullColumnName(), PimItem::idFullColumnName());
848 qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName());
849 qb.addValueCondition(Collection::idFullColumnName(), Query::Equals, collection.id());
850 qb.addValueCondition(Part::storageFullColumnName(), Query::Equals, Part::External);
851 qb.addValueCondition(Part::dataFullColumnName(), Query::IsNot, QVariant());
852 if (!qb.exec()) {
853 qCWarning(AKONADISERVER_LOG) << "Failed to cleanup collection" << collection.name() << "(ID" << collection.id() << "):"
854 << "Failed to query existing payload parts";
855 return false;
856 }
857
858 try {
859 while (qb.query().next()) {
860 ExternalPartStorage::self()->removePartFile(ExternalPartStorage::resolveAbsolutePath(qb.query().value(0).toByteArray()));
861 }
862 } catch (const PartHelperException &e) {
863 qb.query().finish();
864 qCWarning(AKONADISERVER_LOG) << "PartHelperException while cleaning up collection" << collection.name() << "(ID" << collection.id() << "):" << e.what();
865 return false;
866 }
867 qb.query().finish();
868
869 // delete the collection itself, referential actions will do the rest
871 return collection.remove();
872}
873
874static bool recursiveSetResourceId(const Collection &collection, qint64 resourceId)
875{
876 Transaction transaction(DataStore::self(), QStringLiteral("RECURSIVE SET RESOURCEID"));
877
878 QueryBuilder qb(Collection::tableName(), QueryBuilder::Update);
879 qb.addValueCondition(Collection::parentIdColumn(), Query::Equals, collection.id());
880 qb.setColumnValue(Collection::resourceIdColumn(), resourceId);
881 qb.setColumnValue(Collection::remoteIdColumn(), QVariant());
882 qb.setColumnValue(Collection::remoteRevisionColumn(), QVariant());
883 if (!qb.exec()) {
884 qCWarning(AKONADISERVER_LOG) << "Failed to set resource ID" << resourceId << "to collection" << collection.name() << "(ID" << collection.id() << ")";
885 return false;
886 }
887
888 // this is a cross-resource move, so also reset any resource-specific data (RID, RREV, etc)
889 // as well as mark the items dirty to prevent cache purging before they have been written back
890 qb = QueryBuilder(PimItem::tableName(), QueryBuilder::Update);
891 qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, collection.id());
892 qb.setColumnValue(PimItem::remoteIdColumn(), QVariant());
893 qb.setColumnValue(PimItem::remoteRevisionColumn(), QVariant());
895 qb.setColumnValue(PimItem::datetimeColumn(), now);
896 qb.setColumnValue(PimItem::atimeColumn(), now);
897 qb.setColumnValue(PimItem::dirtyColumn(), true);
898 if (!qb.exec()) {
899 qCWarning(AKONADISERVER_LOG) << "Failed reset RID/RREV for PimItems in Collection" << collection.name() << "(ID" << collection.id() << ")";
900 return false;
901 }
902
903 transaction.commit();
904
905 const auto children = collection.children();
906 for (const Collection &col : children) {
907 if (!recursiveSetResourceId(col, resourceId)) {
908 return false;
909 }
910 }
911 return true;
912}
913
914bool DataStore::moveCollection(Collection &collection, const Collection &newParent)
915{
916 if (collection.parentId() == newParent.id()) {
917 return true;
918 }
919
920 if (!m_dbOpened) {
921 return false;
922 }
923
924 if (!newParent.isValid()) {
925 qCWarning(AKONADISERVER_LOG) << "Failed to move collection" << collection.name() << "(ID" << collection.id() << "): invalid destination";
926 return false;
927 }
928
929 const QByteArray oldResource = collection.resource().name().toLatin1();
930
931 int resourceId = collection.resourceId();
932 const Collection source = collection.parent();
933 if (newParent.id() > 0) { // not root
934 resourceId = newParent.resourceId();
935 }
937 return false;
938 }
939
940 collection.setParentId(newParent.id());
941 if (collection.resourceId() != resourceId) {
942 collection.setResourceId(resourceId);
943 collection.setRemoteId(QString());
944 collection.setRemoteRevision(QString());
945 if (!recursiveSetResourceId(collection, resourceId)) {
946 return false;
947 }
948 }
949
950 if (!collection.update()) {
951 qCWarning(AKONADISERVER_LOG) << "Failed to move Collection" << collection.name() << "(ID" << collection.id() << ")"
952 << "into Collection" << collection.name() << "(ID" << collection.id() << ")";
953 return false;
954 }
955
956 notificationCollector()->collectionMoved(collection, source, oldResource, newParent.resource().name().toLatin1());
957 return true;
958}
959
960bool DataStore::appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes)
961{
962 if (mimeTypes.isEmpty()) {
963 return true;
964 }
965
966 for (const QString &mimeType : mimeTypes) {
967 const auto &mt = MimeType::retrieveByNameOrCreate(mimeType);
968 if (!mt.isValid()) {
969 return false;
970 }
971 if (!Collection::addMimeType(collectionId, mt.id())) {
972 qCWarning(AKONADISERVER_LOG) << "Failed to append mimetype" << mt.name() << "to Collection" << collectionId;
973 return false;
974 }
975 }
976
977 return true;
978}
979
981{
982 if (!col.cachePolicyInherit()) {
983 return;
984 }
985
987 while (parent.parentId() != 0) {
988 parent = parent.parent();
989 if (!parent.cachePolicyInherit()) {
990 col.setCachePolicyCheckInterval(parent.cachePolicyCheckInterval());
991 col.setCachePolicyCacheTimeout(parent.cachePolicyCacheTimeout());
992 col.setCachePolicySyncOnDemand(parent.cachePolicySyncOnDemand());
993 col.setCachePolicyLocalParts(parent.cachePolicyLocalParts());
994 return;
995 }
996 }
997
998 // ### system default
999 col.setCachePolicyCheckInterval(-1);
1000 col.setCachePolicyCacheTimeout(-1);
1001 col.setCachePolicySyncOnDemand(false);
1002 col.setCachePolicyLocalParts(QStringLiteral("ALL"));
1003}
1004
1006{
1008 qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), Collection::idFullColumnName(), CollectionPimItemRelation::leftFullColumnName());
1009 qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::Equals, item.id());
1010
1011 if (!qb.exec()) {
1012 qCWarning(AKONADISERVER_LOG) << "Failed to query virtual collections which PimItem" << item.id() << "belongs into";
1013 return QList<Collection>();
1014 }
1015
1016 return qb.result();
1017}
1018
1020{
1021 QueryBuilder qb(CollectionPimItemRelation::tableName(), QueryBuilder::Select);
1022 qb.addJoin(QueryBuilder::InnerJoin, Collection::tableName(), Collection::idFullColumnName(), CollectionPimItemRelation::leftFullColumnName());
1023 qb.addJoin(QueryBuilder::InnerJoin, PimItem::tableName(), PimItem::idFullColumnName(), CollectionPimItemRelation::rightFullColumnName());
1024 qb.addColumn(Collection::idFullColumnName());
1025 qb.addColumns(QStringList() << PimItem::idFullColumnName() << PimItem::remoteIdFullColumnName() << PimItem::remoteRevisionFullColumnName()
1026 << PimItem::mimeTypeIdFullColumnName());
1027 qb.addSortColumn(Collection::idFullColumnName(), Query::Ascending);
1028
1029 if (items.count() == 1) {
1030 qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::Equals, items.first().id());
1031 } else {
1032 QVariantList ids;
1033 ids.reserve(items.count());
1034 for (const PimItem &item : items) {
1035 ids << item.id();
1036 }
1037 qb.addValueCondition(CollectionPimItemRelation::rightFullColumnName(), Query::In, ids);
1038 }
1039
1040 if (!qb.exec()) {
1041 qCWarning(AKONADISERVER_LOG) << "Failed to query virtual Collections which PimItems" << items << "belong into";
1043 }
1044
1045 QSqlQuery query = qb.query();
1047 query.next();
1048 while (query.isValid()) {
1049 const qlonglong collectionId = query.value(0).toLongLong();
1050 QList<PimItem> &pimItems = map[collectionId];
1051 do {
1052 PimItem item;
1053 item.setId(query.value(1).toLongLong());
1054 item.setRemoteId(query.value(2).toString());
1055 item.setRemoteRevision(query.value(3).toString());
1056 item.setMimeTypeId(query.value(4).toLongLong());
1057 pimItems << item;
1058 } while (query.next() && query.value(0).toLongLong() == collectionId);
1059 }
1060 query.finish();
1061
1062 return map;
1063}
1064
1065/* --- PimItem ------------------------------------------------------- */
1066bool DataStore::appendPimItem(QList<Part> &parts,
1067 const QList<Flag> &flags,
1068 const MimeType &mimetype,
1069 const Collection &collection,
1070 const QDateTime &dateTime,
1071 const QString &remote_id,
1072 const QString &remoteRevision,
1073 const QString &gid,
1074 PimItem &pimItem)
1075{
1076 pimItem.setMimeTypeId(mimetype.id());
1077 pimItem.setCollectionId(collection.id());
1078 if (dateTime.isValid()) {
1079 pimItem.setDatetime(dateTime);
1080 }
1081 if (remote_id.isEmpty()) {
1082 // from application
1083 pimItem.setDirty(true);
1084 } else {
1085 // from resource
1086 pimItem.setRemoteId(remote_id);
1087 pimItem.setDirty(false);
1088 }
1089 pimItem.setRemoteRevision(remoteRevision);
1090 pimItem.setGid(gid);
1092
1093 if (!pimItem.insert()) {
1094 qCWarning(AKONADISERVER_LOG) << "Failed to append new PimItem into Collection" << collection.name() << "(ID" << collection.id() << ")";
1095 return false;
1096 }
1097
1098 // insert every part
1099 if (!parts.isEmpty()) {
1100 // don't use foreach, the caller depends on knowing the part has changed, see the Append handler
1101 for (QList<Part>::iterator it = parts.begin(); it != parts.end(); ++it) {
1102 (*it).setPimItemId(pimItem.id());
1103 if ((*it).datasize() < (*it).data().size()) {
1104 (*it).setDatasize((*it).data().size());
1105 }
1106
1107 // qCDebug(AKONADISERVER_LOG) << "Insert from DataStore::appendPimItem";
1108 if (!PartHelper::insert(&(*it))) {
1109 qCWarning(AKONADISERVER_LOG) << "Failed to add part" << it->partType().name() << "to new PimItem" << pimItem.id();
1110 return false;
1111 }
1112 }
1113 }
1114
1115 bool seen = false;
1116 for (const Flag &flag : flags) {
1118 if (!pimItem.addFlag(flag)) {
1119 qCWarning(AKONADISERVER_LOG) << "Failed to add flag" << flag.name() << "to new PimItem" << pimItem.id();
1120 return false;
1121 }
1122 }
1123
1124 // qCDebug(AKONADISERVER_LOG) << "appendPimItem: " << pimItem;
1125
1127 return true;
1128}
1129
1130bool DataStore::unhidePimItem(PimItem &pimItem)
1131{
1132 if (!m_dbOpened) {
1133 return false;
1134 }
1135
1136 qCDebug(AKONADISERVER_LOG) << "DataStore::unhidePimItem(" << pimItem << ")";
1137
1138 // FIXME: This is inefficient. Using a bit on the PimItemTable record would probably be some orders of magnitude faster...
1139 return removeItemParts(pimItem, {AKONADI_ATTRIBUTE_HIDDEN});
1140}
1141
1143{
1144 if (!m_dbOpened) {
1145 return false;
1146 }
1147
1148 qCDebug(AKONADISERVER_LOG) << "DataStore::unhideAllPimItems()";
1149
1150 try {
1151 return PartHelper::remove(Part::partTypeIdFullColumnName(), PartTypeHelper::fromFqName(QStringLiteral("ATR"), QStringLiteral("HIDDEN")).id());
1152 } catch (...) {
1153 } // we can live with this failing
1154
1155 return false;
1156}
1157
1158bool DataStore::cleanupPimItems(const PimItem::List &items, bool silent)
1159{
1160 // generate relation removed notifications
1161 if (!silent) {
1162 for (const PimItem &item : items) {
1164 relationQuery.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, item.id());
1165 relationQuery.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, item.id());
1166 relationQuery.setSubQueryMode(Query::Or);
1167
1168 if (!relationQuery.exec()) {
1169 throw HandlerException("Failed to obtain relations");
1170 }
1171 const Relation::List relations = relationQuery.result();
1172 for (const Relation &relation : relations) {
1174 }
1175 }
1176
1177 // generate the notification before actually removing the data
1179 }
1180
1181 // FIXME: Create a single query to do this
1182 for (const auto &item : items) {
1183 if (!item.clearFlags()) {
1184 qCWarning(AKONADISERVER_LOG) << "Failed to clean up flags from PimItem" << item.id();
1185 return false;
1186 }
1187 if (!PartHelper::remove(Part::pimItemIdColumn(), item.id())) {
1188 qCWarning(AKONADISERVER_LOG) << "Failed to clean up parts from PimItem" << item.id();
1189 return false;
1190 }
1191 if (!PimItem::remove(PimItem::idColumn(), item.id())) {
1192 qCWarning(AKONADISERVER_LOG) << "Failed to remove PimItem" << item.id();
1193 return false;
1194 }
1195
1196 if (!Entity::clearRelation<CollectionPimItemRelation>(item.id(), Entity::Right)) {
1197 qCWarning(AKONADISERVER_LOG) << "Failed to remove PimItem" << item.id() << "from linked collections";
1198 return false;
1199 }
1200 }
1201
1202 return true;
1203}
1204
1205bool DataStore::addCollectionAttribute(const Collection &col, const QByteArray &key, const QByteArray &value, bool silent)
1206{
1208 qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, col.id());
1209 qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, key);
1210 if (!qb.exec()) {
1211 qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id()
1212 << "): Failed to query existing attribute";
1213 return false;
1214 }
1215
1216 if (!qb.result().isEmpty()) {
1217 qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id()
1218 << "): Attribute already exists";
1219 return false;
1220 }
1221
1223 attr.setCollectionId(col.id());
1224 attr.setType(key);
1225 attr.setValue(value);
1226
1227 if (!attr.insert()) {
1228 qCWarning(AKONADISERVER_LOG) << "Failed to append attribute" << key << "to Collection" << col.name() << "(ID" << col.id() << ")";
1229 return false;
1230 }
1231
1232 if (!silent) {
1234 }
1235 return true;
1236}
1237
1239{
1241 qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, col.id());
1242 qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, key);
1243 if (!qb.exec()) {
1244 throw HandlerException("Unable to query for collection attribute");
1245 }
1246
1247 const QList<CollectionAttribute> result = qb.result();
1248 for (CollectionAttribute attr : result) {
1249 if (!attr.remove()) {
1250 throw HandlerException("Unable to remove collection attribute");
1251 }
1252 }
1253
1254 if (!result.isEmpty()) {
1256 return true;
1257 }
1258 return false;
1259}
1260
1261void DataStore::debugLastDbError(QStringView actionDescription) const
1262{
1263 qCCritical(AKONADISERVER_LOG) << "Database error:" << actionDescription;
1264 qCCritical(AKONADISERVER_LOG) << " Last driver error:" << m_database.lastError().driverText();
1265 qCCritical(AKONADISERVER_LOG) << " Last database error:" << m_database.lastError().databaseText();
1266
1267 if (m_akonadi) {
1268 m_akonadi->tracer().error("DataStore (Database Error)",
1269 QStringLiteral("%1\nDriver said: %2\nDatabase said:%3")
1270 .arg(actionDescription, m_database.lastError().driverText(), m_database.lastError().databaseText()));
1271 }
1272}
1273
1274void DataStore::debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const
1275{
1276 qCCritical(AKONADISERVER_LOG) << "Query error:" << actionDescription;
1277 qCCritical(AKONADISERVER_LOG) << " Last error message:" << query.lastError().text();
1278 qCCritical(AKONADISERVER_LOG) << " Last driver error:" << m_database.lastError().driverText();
1279 qCCritical(AKONADISERVER_LOG) << " Last database error:" << m_database.lastError().databaseText();
1280
1281 if (m_akonadi) {
1282 m_akonadi->tracer().error("DataStore (Database Query Error)",
1283 QStringLiteral("%1: %2").arg(QString::fromLatin1(actionDescription), query.lastError().text()));
1284 }
1285}
1286
1287// static
1288QString DataStore::dateTimeFromQDateTime(const QDateTime &dateTime)
1289{
1290 QDateTime utcDateTime = dateTime;
1291 if (utcDateTime.timeSpec() != Qt::UTC) {
1292 utcDateTime = utcDateTime.toUTC();
1293 }
1294 return utcDateTime.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"));
1295}
1296
1297// static
1298QDateTime DataStore::dateTimeToQDateTime(const QByteArray &dateTime)
1299{
1300 return QDateTime::fromString(QString::fromLatin1(dateTime), QStringLiteral("yyyy-MM-dd hh:mm:ss"));
1301}
1302
1303bool DataStore::doRollback()
1304{
1305 QSqlDriver *driver = m_database.driver();
1307 timer.start();
1308 driver->rollbackTransaction();
1309 StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), false, timer.elapsed(), m_database.lastError().text());
1310 if (m_database.lastError().isValid()) {
1311 debugLastDbError(u"DataStore::rollbackTransaction");
1312 return false;
1313 }
1314 return true;
1315}
1316
1317void DataStore::transactionKilledByDB()
1318{
1319 m_transactionKilledByDB = true;
1320 cleanupAfterRollback();
1322}
1323
1325{
1326 if (!m_dbOpened) {
1327 return false;
1328 }
1329
1330 if (m_transactionLevel == 0 || m_transactionKilledByDB) {
1331 m_transactionKilledByDB = false;
1333 timer.start();
1334 if (DbType::type(m_database) == DbType::Sqlite) {
1335 m_database.exec(QStringLiteral("BEGIN IMMEDIATE TRANSACTION"));
1336 StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), name, timer.elapsed(), m_database.lastError().text());
1337 if (m_database.lastError().isValid()) {
1338 debugLastDbError(QStringLiteral("DataStore::beginTransaction (SQLITE) name: %1").arg(name));
1339 return false;
1340 }
1341 } else {
1342 m_database.driver()->beginTransaction();
1343 StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), name, timer.elapsed(), m_database.lastError().text());
1344 if (m_database.lastError().isValid()) {
1345 debugLastDbError(u"DataStore::beginTransaction");
1346 return false;
1347 }
1348 }
1349
1350 if (DbType::type(m_database) == DbType::PostgreSQL) {
1351 // Make constraints check deferred in PostgreSQL. Allows for
1352 // INSERT INTO mimetypetable (name) VALUES ('foo') RETURNING id;
1353 // INSERT INTO collectionmimetyperelation (collection_id, mimetype_id) VALUES (x, y)
1354 // where "y" refers to the newly inserted mimetype
1355 m_database.exec(QStringLiteral("SET CONSTRAINTS ALL DEFERRED"));
1356 }
1357 }
1358
1359 ++m_transactionLevel;
1360
1361 return true;
1362}
1363
1365{
1366 if (!m_dbOpened) {
1367 return false;
1368 }
1369
1370 if (m_transactionLevel == 0) {
1371 qCWarning(AKONADISERVER_LOG) << "DataStore::rollbackTransaction(): No transaction in progress!";
1372 return false;
1373 }
1374
1375 --m_transactionLevel;
1376
1377 if (m_transactionLevel == 0 && !m_transactionKilledByDB) {
1378 doRollback();
1379 cleanupAfterRollback();
1381 }
1382
1383 return true;
1384}
1385
1387{
1388 if (!m_dbOpened) {
1389 return false;
1390 }
1391
1392 if (m_transactionLevel == 0) {
1393 qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): No transaction in progress!";
1394 return false;
1395 }
1396
1397 if (m_transactionLevel == 1) {
1398 if (m_transactionKilledByDB) {
1399 qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): Cannot commit, transaction was killed by mysql deadlock handling!";
1400 return false;
1401 }
1402 QSqlDriver *driver = m_database.driver();
1404 timer.start();
1405 driver->commitTransaction();
1406 StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), true, timer.elapsed(), m_database.lastError().text());
1407 if (m_database.lastError().isValid()) {
1408 debugLastDbError(u"DataStore::commitTransaction");
1410 return false;
1411 } else {
1412 m_transactionLevel--;
1414 }
1415 } else {
1416 m_transactionLevel--;
1417 }
1418 return true;
1419}
1420
1422{
1423 return m_transactionLevel > 0;
1424}
1425
1426void DataStore::sendKeepAliveQuery()
1427{
1428 if (m_database.isOpen()) {
1429 QSqlQuery query(m_database);
1430 query.exec(QStringLiteral("SELECT 1"));
1431 }
1432}
1433
1434void DataStore::cleanupAfterRollback()
1435{
1436 MimeType::invalidateCompleteCache();
1437 Flag::invalidateCompleteCache();
1439 Collection::invalidateCompleteCache();
1440 PartType::invalidateCompleteCache();
1441 if (m_akonadi) {
1442 m_akonadi->collectionStatistics().expireCache();
1443 }
1445}
1446
1447#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.
An Akonadi Relation.
Definition relation.h:41
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
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 relationRemoved(const Relation &relation)
Notify about a removed relation.
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.
bool exec()
Executes the query, returns true on success.
void setColumnValue(const QString &column, const QVariant &value)
Sets a column to the given value (only valid for INSERT and UPDATE queries).
@ 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
Helper class for creating and executing database SELECT queries.
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)
const QList< QKeySequence > & end()
A glue between Qt and the standard library.
QDateTime currentDateTimeUtc()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() 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
T qobject_cast(QObject *object)
QSqlQuery exec(const QString &query) 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
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
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 11:57:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.