File indexing completed on 2025-01-05 04:46:58

0001 /*
0002     SPDX-FileCopyrightText: 2007-2012 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dbupdater.h"
0008 #include "akonadischema.h"
0009 #include "akonadiserver_debug.h"
0010 #include "datastore.h"
0011 #include "dbconfig.h"
0012 #include "dbinitializer_p.h"
0013 #include "dbintrospector.h"
0014 #include "dbtype.h"
0015 #include "entities.h"
0016 #include "querybuilder.h"
0017 #include "selectquerybuilder.h"
0018 
0019 #include "private/dbus_p.h"
0020 
0021 #include <QCoreApplication>
0022 #include <QDBusConnection>
0023 #include <QDBusError>
0024 #include <QMetaMethod>
0025 #include <QSqlError>
0026 #include <QSqlQuery>
0027 #include <QThread>
0028 
0029 #include <QDomDocument>
0030 #include <QElapsedTimer>
0031 #include <QFile>
0032 #include <QSqlResult>
0033 
0034 using namespace Akonadi;
0035 using namespace Akonadi::Server;
0036 
0037 DbUpdater::DbUpdater(const QSqlDatabase &database, const QString &filename)
0038     : m_database(database)
0039     , m_filename(filename)
0040 {
0041 }
0042 
0043 bool DbUpdater::run()
0044 {
0045     // TODO error handling
0046     auto store = DataStore::dataStoreForDatabase(m_database);
0047     auto currentVersion = SchemaVersion::retrieveAll(store).at(0);
0048 
0049     UpdateSet::Map updates;
0050 
0051     if (!parseUpdateSets(currentVersion.version(), updates)) {
0052         return false;
0053     }
0054 
0055     if (updates.isEmpty()) {
0056         return true;
0057     }
0058 
0059     // indicate clients this might take a while
0060     // we can ignore unregistration in error cases, that'll kill the server anyway
0061     if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::UpgradeIndicator))) {
0062         qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message();
0063     }
0064 
0065     // QMap is sorted, so we should be replaying the changes in correct order
0066     for (QMap<int, UpdateSet>::ConstIterator it = updates.constBegin(); it != updates.constEnd(); ++it) {
0067         Q_ASSERT(it.key() > currentVersion.version());
0068         qCDebug(AKONADISERVER_LOG) << "DbUpdater: update to version:" << it.key() << " mandatory:" << it.value().abortOnFailure;
0069 
0070         bool success = false;
0071         bool hasTransaction = false;
0072         if (it.value().complex) { // complex update
0073             const QString methodName = QStringLiteral("complexUpdate_%1()").arg(it.value().version);
0074             const int index = metaObject()->indexOfMethod(methodName.toLatin1().constData());
0075             if (index == -1) {
0076                 success = false;
0077                 qCCritical(AKONADISERVER_LOG) << "Update to version" << it.value().version << "marked as complex, but no implementation is available";
0078             } else {
0079                 const QMetaMethod method = metaObject()->method(index);
0080                 method.invoke(this, Q_RETURN_ARG(bool, success));
0081                 if (!success) {
0082                     qCCritical(AKONADISERVER_LOG) << "Update failed";
0083                 }
0084             }
0085         } else { // regular update
0086             success = m_database.transaction();
0087             if (success) {
0088                 hasTransaction = true;
0089                 const QStringList statements = it.value().statements;
0090                 for (const QString &statement : statements) {
0091                     QSqlQuery query(m_database);
0092                     success = query.exec(statement);
0093                     if (!success) {
0094                         qCCritical(AKONADISERVER_LOG) << "DBUpdater: query error:" << query.lastError().text() << m_database.lastError().text();
0095                         qCCritical(AKONADISERVER_LOG) << "Query was: " << statement;
0096                         qCCritical(AKONADISERVER_LOG) << "Target version was: " << it.key();
0097                         qCCritical(AKONADISERVER_LOG) << "Mandatory: " << it.value().abortOnFailure;
0098                     }
0099                 }
0100             }
0101         }
0102 
0103         if (success) {
0104             currentVersion.setVersion(it.key());
0105             success = currentVersion.update();
0106         }
0107 
0108         if (!success || (hasTransaction && !m_database.commit())) {
0109             qCCritical(AKONADISERVER_LOG) << "Failed to commit transaction for database update";
0110             if (hasTransaction) {
0111                 m_database.rollback();
0112             }
0113             if (it.value().abortOnFailure) {
0114                 return false;
0115             }
0116         }
0117     }
0118 
0119     QDBusConnection::sessionBus().unregisterService(DBus::serviceName(DBus::UpgradeIndicator));
0120     return true;
0121 }
0122 
0123 bool DbUpdater::parseUpdateSets(int currentVersion, UpdateSet::Map &updates) const
0124 {
0125     QFile file(m_filename);
0126     if (!file.open(QIODevice::ReadOnly)) {
0127         qCCritical(AKONADISERVER_LOG) << "Unable to open update description file" << m_filename;
0128         return false;
0129     }
0130 
0131     QDomDocument document;
0132 
0133     const auto result = document.setContent(&file);
0134     if (!result) {
0135         qCCritical(AKONADISERVER_LOG) << "Unable to parse update description file" << m_filename << ":" << result.errorMessage << "at line" << result.errorLine
0136                                       << "column" << result.errorColumn;
0137         return false;
0138     }
0139 
0140     const QDomElement documentElement = document.documentElement();
0141     if (documentElement.tagName() != QLatin1StringView("updates")) {
0142         qCCritical(AKONADISERVER_LOG) << "Invalid update description file format";
0143         return false;
0144     }
0145 
0146     // iterate over the xml document and extract update information into an UpdateSet
0147     QDomElement updateElement = documentElement.firstChildElement();
0148     while (!updateElement.isNull()) {
0149         if (updateElement.tagName() == QLatin1StringView("update")) {
0150             const int version = updateElement.attribute(QStringLiteral("version"), QStringLiteral("-1")).toInt();
0151             if (version <= 0) {
0152                 qCCritical(AKONADISERVER_LOG) << "Invalid version attribute in database update description";
0153                 return false;
0154             }
0155 
0156             if (updates.contains(version)) {
0157                 qCCritical(AKONADISERVER_LOG) << "Duplicate version attribute in database update description";
0158                 return false;
0159             }
0160 
0161             if (version <= currentVersion) {
0162                 qCDebug(AKONADISERVER_LOG) << "skipping update" << version;
0163             } else {
0164                 UpdateSet updateSet;
0165                 updateSet.version = version;
0166                 updateSet.abortOnFailure = (updateElement.attribute(QStringLiteral("abortOnFailure")) == QLatin1StringView("true"));
0167 
0168                 QDomElement childElement = updateElement.firstChildElement();
0169                 while (!childElement.isNull()) {
0170                     if (childElement.tagName() == QLatin1StringView("raw-sql")) {
0171                         if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) {
0172                             updateSet.statements << buildRawSqlStatement(childElement);
0173                         }
0174                     } else if (childElement.tagName() == QLatin1StringView("complex-update")) {
0175                         if (updateApplicable(childElement.attribute(QStringLiteral("backends")))) {
0176                             updateSet.complex = true;
0177                         }
0178                     }
0179                     // TODO: check for generic tags here in the future
0180 
0181                     childElement = childElement.nextSiblingElement();
0182                 }
0183 
0184                 if (!updateSet.statements.isEmpty() || updateSet.complex) {
0185                     updates.insert(version, updateSet);
0186                 }
0187             }
0188         }
0189         updateElement = updateElement.nextSiblingElement();
0190     }
0191 
0192     return true;
0193 }
0194 
0195 bool DbUpdater::updateApplicable(const QString &backends) const
0196 {
0197     const QStringList matchingBackends = backends.split(QLatin1Char(','));
0198 
0199     QString currentBackend;
0200     switch (DbType::type(m_database)) {
0201     case DbType::MySQL:
0202         currentBackend = QStringLiteral("mysql");
0203         break;
0204     case DbType::PostgreSQL:
0205         currentBackend = QStringLiteral("psql");
0206         break;
0207     case DbType::Sqlite:
0208         currentBackend = QStringLiteral("sqlite");
0209         break;
0210     case DbType::Unknown:
0211         return false;
0212     }
0213 
0214     return matchingBackends.contains(currentBackend);
0215 }
0216 
0217 QString DbUpdater::buildRawSqlStatement(const QDomElement &element) const
0218 {
0219     return element.text().trimmed();
0220 }
0221 
0222 bool DbUpdater::complexUpdate_25()
0223 {
0224     qCDebug(AKONADISERVER_LOG) << "Starting database update to version 25";
0225 
0226     DbType::Type dbType = DbType::type(m_database);
0227     auto store = DataStore::dataStoreForDatabase(m_database);
0228 
0229     QElapsedTimer ttotal;
0230     ttotal.start();
0231 
0232     // Recover from possibly failed or interrupted update
0233     {
0234         // We don't care if this fails, it just means that there was no failed update
0235         QSqlQuery query(m_database);
0236         query.exec(QStringLiteral("ALTER TABLE PartTable_old RENAME TO PartTable"));
0237     }
0238 
0239     {
0240         QSqlQuery query(m_database);
0241         query.exec(QStringLiteral("DROP TABLE IF EXISTS PartTable_new"));
0242     }
0243 
0244     {
0245         // Make sure the table is empty, otherwise we get duplicate key error
0246         QSqlQuery query(m_database);
0247         if (dbType == DbType::Sqlite) {
0248             query.exec(QStringLiteral("DELETE FROM PartTypeTable"));
0249         } else { // MySQL, PostgreSQL
0250             query.exec(QStringLiteral("TRUNCATE TABLE PartTypeTable"));
0251         }
0252     }
0253 
0254     {
0255         // It appears that more users than expected have the invalid "GID" part in their
0256         // PartTable, which breaks the migration below (see BKO#331867), so we apply this
0257         // wanna-be fix to remove the invalid part before we start the actual migration.
0258         QueryBuilder qb(store, QStringLiteral("PartTable"), QueryBuilder::Delete);
0259         qb.addValueCondition(QStringLiteral("PartTable.name"), Query::Equals, QLatin1StringView("GID"));
0260         qb.exec();
0261     }
0262 
0263     qCDebug(AKONADISERVER_LOG) << "Creating a PartTable_new";
0264     {
0265         TableDescription description;
0266         description.name = QStringLiteral("PartTable_new");
0267 
0268         ColumnDescription idColumn;
0269         idColumn.name = QStringLiteral("id");
0270         idColumn.type = QStringLiteral("qint64");
0271         idColumn.isAutoIncrement = true;
0272         idColumn.isPrimaryKey = true;
0273         description.columns << idColumn;
0274 
0275         ColumnDescription pimItemIdColumn;
0276         pimItemIdColumn.name = QStringLiteral("pimItemId");
0277         pimItemIdColumn.type = QStringLiteral("qint64");
0278         pimItemIdColumn.allowNull = false;
0279         description.columns << pimItemIdColumn;
0280 
0281         ColumnDescription partTypeIdColumn;
0282         partTypeIdColumn.name = QStringLiteral("partTypeId");
0283         partTypeIdColumn.type = QStringLiteral("qint64");
0284         partTypeIdColumn.allowNull = false;
0285         description.columns << partTypeIdColumn;
0286 
0287         ColumnDescription dataColumn;
0288         dataColumn.name = QStringLiteral("data");
0289         dataColumn.type = QStringLiteral("QByteArray");
0290         description.columns << dataColumn;
0291 
0292         ColumnDescription dataSizeColumn;
0293         dataSizeColumn.name = QStringLiteral("datasize");
0294         dataSizeColumn.type = QStringLiteral("qint64");
0295         dataSizeColumn.allowNull = false;
0296         description.columns << dataSizeColumn;
0297 
0298         ColumnDescription versionColumn;
0299         versionColumn.name = QStringLiteral("version");
0300         versionColumn.type = QStringLiteral("int");
0301         versionColumn.defaultValue = QStringLiteral("0");
0302         description.columns << versionColumn;
0303 
0304         ColumnDescription externalColumn;
0305         externalColumn.name = QStringLiteral("external");
0306         externalColumn.type = QStringLiteral("bool");
0307         externalColumn.defaultValue = QStringLiteral("false");
0308         description.columns << externalColumn;
0309 
0310         DbInitializer::Ptr initializer = DbInitializer::createInstance(m_database);
0311         const QString queryString = initializer->buildCreateTableStatement(description);
0312 
0313         QSqlQuery query(m_database);
0314         if (!query.exec(queryString)) {
0315             qCCritical(AKONADISERVER_LOG) << query.lastError().text();
0316             return false;
0317         }
0318     }
0319 
0320     qCDebug(AKONADISERVER_LOG) << "Migrating part types";
0321     {
0322         // Get list of all part names
0323         QueryBuilder qb(store, QStringLiteral("PartTable"), QueryBuilder::Select);
0324         qb.setDistinct(true);
0325         qb.addColumn(QStringLiteral("PartTable.name"));
0326 
0327         if (!qb.exec()) {
0328             qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text();
0329             return false;
0330         }
0331 
0332         // Process them one by one
0333         QSqlQuery query = qb.query();
0334         while (query.next()) {
0335             // Split the part name to namespace and name and insert it to PartTypeTable
0336             const QString partName = query.value(0).toString();
0337             const QString ns = partName.left(3);
0338             const QString name = partName.mid(4);
0339 
0340             {
0341                 QueryBuilder qb(store, QStringLiteral("PartTypeTable"), QueryBuilder::Insert);
0342                 qb.setColumnValue(QStringLiteral("ns"), ns);
0343                 qb.setColumnValue(QStringLiteral("name"), name);
0344                 if (!qb.exec()) {
0345                     qCCritical(AKONADISERVER_LOG) << qb.query().lastError().text();
0346                     return false;
0347                 }
0348             }
0349             qCDebug(AKONADISERVER_LOG) << "\t Moved part type" << partName << "to PartTypeTable";
0350         }
0351         query.finish();
0352     }
0353 
0354     qCDebug(AKONADISERVER_LOG) << "Migrating data from PartTable to PartTable_new";
0355     {
0356         QSqlQuery query(m_database);
0357         QString queryString;
0358         if (dbType == DbType::PostgreSQL) {
0359             queryString = QStringLiteral(
0360                 "INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) "
0361                 "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, "
0362                 "       PartTable.datasize, PartTable.version, PartTable.external "
0363                 "FROM PartTable "
0364                 "LEFT JOIN PartTypeTable ON "
0365                 "          PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)");
0366         } else if (dbType == DbType::MySQL) {
0367             queryString = QStringLiteral(
0368                 "INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) "
0369                 "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, "
0370                 "PartTable.datasize, PartTable.version, PartTable.external "
0371                 "FROM PartTable "
0372                 "LEFT JOIN PartTypeTable ON PartTable.name = CONCAT(PartTypeTable.ns, ':', PartTypeTable.name)");
0373         } else if (dbType == DbType::Sqlite) {
0374             queryString = QStringLiteral(
0375                 "INSERT INTO PartTable_new (id, pimItemId, partTypeId, data, datasize, version, external) "
0376                 "SELECT PartTable.id, PartTable.pimItemId, PartTypeTable.id, PartTable.data, "
0377                 "PartTable.datasize, PartTable.version, PartTable.external "
0378                 "FROM PartTable "
0379                 "LEFT JOIN PartTypeTable ON PartTable.name = PartTypeTable.ns || ':' || PartTypeTable.name");
0380         }
0381 
0382         if (!query.exec(queryString)) {
0383             qCCritical(AKONADISERVER_LOG) << query.lastError().text();
0384             return false;
0385         }
0386     }
0387 
0388     qCDebug(AKONADISERVER_LOG) << "Swapping PartTable_new for PartTable";
0389     {
0390         // Does an atomic swap
0391 
0392         QSqlQuery query(m_database);
0393 
0394         if (dbType == DbType::PostgreSQL || dbType == DbType::Sqlite) {
0395             if (dbType == DbType::PostgreSQL) {
0396                 m_database.transaction();
0397             }
0398 
0399             if (!query.exec(QStringLiteral("ALTER TABLE PartTable RENAME TO PartTable_old"))) {
0400                 qCCritical(AKONADISERVER_LOG) << query.lastError().text();
0401                 m_database.rollback();
0402                 return false;
0403             }
0404 
0405             // If this fails in SQLite (i.e. without transaction), we can still recover on next start)
0406             if (!query.exec(QStringLiteral("ALTER TABLE PartTable_new RENAME TO PartTable"))) {
0407                 qCCritical(AKONADISERVER_LOG) << query.lastError().text();
0408                 m_database.rollback();
0409                 return false;
0410             }
0411 
0412             if (dbType == DbType::PostgreSQL) {
0413                 m_database.commit();
0414             }
0415         } else { // MySQL cannot do rename in transaction, but supports atomic renames
0416             if (!query.exec(QStringLiteral("RENAME TABLE PartTable TO PartTable_old,"
0417                                            "             PartTable_new TO PartTable"))) {
0418                 qCCritical(AKONADISERVER_LOG) << query.lastError().text();
0419                 return false;
0420             }
0421         }
0422     }
0423 
0424     qCDebug(AKONADISERVER_LOG) << "Removing PartTable_old";
0425     {
0426         QSqlQuery query(m_database);
0427         if (!query.exec(QStringLiteral("DROP TABLE PartTable_old;"))) {
0428             // It does not matter when this fails, we are successfully migrated
0429             qCDebug(AKONADISERVER_LOG) << query.lastError().text();
0430             qCDebug(AKONADISERVER_LOG) << "Not a fatal problem, continuing...";
0431         }
0432     }
0433 
0434     // Fine tuning for PostgreSQL
0435     qCDebug(AKONADISERVER_LOG) << "Final tuning of new PartTable";
0436     {
0437         QSqlQuery query(m_database);
0438         if (dbType == DbType::PostgreSQL) {
0439             query.exec(QStringLiteral("ALTER TABLE PartTable RENAME CONSTRAINT parttable_new_pkey TO parttable_pkey"));
0440             query.exec(QStringLiteral("ALTER SEQUENCE parttable_new_id_seq RENAME TO parttable_id_seq"));
0441             query.exec(QStringLiteral("SELECT setval('parttable_id_seq', MAX(id) + 1) FROM PartTable"));
0442         } else if (dbType == DbType::MySQL) {
0443             // 0 will automatically reset AUTO_INCREMENT to SELECT MAX(id) + 1 FROM PartTable
0444             query.exec(QStringLiteral("ALTER TABLE PartTable AUTO_INCREMENT = 0"));
0445         }
0446     }
0447 
0448     qCDebug(AKONADISERVER_LOG) << "Update done in" << ttotal.elapsed() << "ms";
0449 
0450     // Foreign keys and constraints will be reconstructed automatically once
0451     // all updates are done
0452 
0453     return true;
0454 }
0455 
0456 bool DbUpdater::complexUpdate_36()
0457 {
0458     qCDebug(AKONADISERVER_LOG, "Starting database update to version 36");
0459     Q_ASSERT(DbType::type(m_database) == DbType::Sqlite);
0460 
0461     QSqlQuery query(m_database);
0462     if (!query.exec(QStringLiteral("PRAGMA foreign_key_checks=OFF"))) {
0463         qCCritical(AKONADISERVER_LOG, "Failed to disable foreign key checks!");
0464         return false;
0465     }
0466 
0467     const auto hasForeignKeys = [](const TableDescription &desc) {
0468         return std::any_of(desc.columns.cbegin(), desc.columns.cend(), [](const ColumnDescription &col) {
0469             return !col.refTable.isEmpty() && !col.refColumn.isEmpty();
0470         });
0471     };
0472 
0473     const auto recreateTableWithForeignKeys = [this](const TableDescription &table) -> QPair<bool, QSqlQuery> {
0474         qCDebug(AKONADISERVER_LOG) << "Updating foreign keys in table" << table.name;
0475 
0476         QSqlQuery query(m_database);
0477 
0478         // Recover from possibly failed or interrupted update
0479         // We don't care if this fails, it just means that there was no failed update
0480         query.exec(QStringLiteral("ALTER TABLE %1_old RENAME TO %1").arg(table.name));
0481         query.exec(QStringLiteral("DROP TABLE %1_new").arg(table.name));
0482 
0483         qCDebug(AKONADISERVER_LOG, "\tCreating table %s_new with foreign keys", qUtf8Printable(table.name));
0484         {
0485             const auto initializer = DbInitializer::createInstance(m_database);
0486             TableDescription copy = table;
0487             copy.name += QStringLiteral("_new");
0488             if (!query.exec(initializer->buildCreateTableStatement(copy))) {
0489                 // If this fails we will recover on next start
0490                 return {false, query};
0491             }
0492         }
0493 
0494         qCDebug(AKONADISERVER_LOG,
0495                 "\tCopying values from %s to %s_new (this may take a very long of time...)",
0496                 qUtf8Printable(table.name),
0497                 qUtf8Printable(table.name));
0498         if (!query.exec(QStringLiteral("INSERT INTO %1_new SELECT * FROM %1").arg(table.name))) {
0499             // If this fails, we will recover on next start
0500             return {false, query};
0501         }
0502 
0503         qCDebug(AKONADISERVER_LOG, "\tSwapping %s_new for %s", qUtf8Printable(table.name), qUtf8Printable(table.name));
0504         if (!query.exec(QStringLiteral("ALTER TABLE %1 RENAME TO %1_old").arg(table.name))) {
0505             // If this fails we will recover on next start
0506             return {false, query};
0507         }
0508 
0509         if (!query.exec(QStringLiteral("ALTER TABLE %1_new RENAME TO %1").arg(table.name))) {
0510             // If this fails we will recover on next start
0511             return {false, query};
0512         }
0513 
0514         qCDebug(AKONADISERVER_LOG, "\tRemoving table %s_old", qUtf8Printable(table.name));
0515         if (!query.exec(QStringLiteral("DROP TABLE %1_old").arg(table.name))) {
0516             // We don't care if this fails
0517             qCWarning(AKONADISERVER_LOG, "Failed to DROP TABLE %s (not fatal, update will continue)", qUtf8Printable(table.name));
0518             qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text()));
0519         }
0520 
0521         qCDebug(AKONADISERVER_LOG) << "\tOptimizing table %s", qUtf8Printable(table.name);
0522         if (!query.exec(QStringLiteral("ANALYZE %1").arg(table.name))) {
0523             // We don't care if this fails
0524             qCWarning(AKONADISERVER_LOG, "Failed to ANALYZE %s (not fatal, update will continue)", qUtf8Printable(table.name));
0525             qCWarning(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text()));
0526         }
0527 
0528         qCDebug(AKONADISERVER_LOG) << "\tDone";
0529         return {true, QSqlQuery()};
0530     };
0531 
0532     AkonadiSchema schema;
0533     const auto tables = schema.tables();
0534     for (const auto &table : tables) {
0535         if (!hasForeignKeys(table)) {
0536             continue;
0537         }
0538 
0539         const auto &[ok, query] = recreateTableWithForeignKeys(table);
0540         if (!ok) {
0541             qCCritical(AKONADISERVER_LOG, "SQL error when updating table %s", qUtf8Printable(table.name));
0542             qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(query.executedQuery()));
0543             qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text()));
0544             return false;
0545         }
0546     }
0547 
0548     const auto relations = schema.relations();
0549     for (const auto &relation : relations) {
0550         const RelationTableDescription table(relation);
0551         const auto &[ok, query] = recreateTableWithForeignKeys(table);
0552         if (!ok) {
0553             qCCritical(AKONADISERVER_LOG, "SQL error when updating relation table %s", qUtf8Printable(table.name));
0554             qCCritical(AKONADISERVER_LOG, "Query: %s", qUtf8Printable(query.executedQuery()));
0555             qCCritical(AKONADISERVER_LOG, "Error: %s", qUtf8Printable(query.lastError().text()));
0556             return false;
0557         }
0558     }
0559 
0560     qCDebug(AKONADISERVER_LOG) << "Running VACUUM to reduce DB size";
0561     if (!query.exec(QStringLiteral("VACUUM"))) {
0562         qCWarning(AKONADISERVER_LOG) << "Vacuum failed (not fatal, update will continue)";
0563         qCWarning(AKONADISERVER_LOG) << "Error:" << query.lastError().text();
0564     }
0565 
0566     return true;
0567 }
0568 
0569 #include "moc_dbupdater.cpp"