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"