Changeset View
Changeset View
Standalone View
Standalone View
src/server/storage/datastore.cpp
Show All 15 Lines | |||||
16 | * License along with this program; if not, write to the * | 16 | * License along with this program; if not, write to the * | ||
17 | * Free Software Foundation, Inc., * | 17 | * Free Software Foundation, Inc., * | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * | ||
19 | ***************************************************************************/ | 19 | ***************************************************************************/ | ||
20 | 20 | | |||
21 | #include "datastore.h" | 21 | #include "datastore.h" | ||
22 | 22 | | |||
23 | #include "akonadi.h" | 23 | #include "akonadi.h" | ||
24 | #include "collectionstatistics.h" | ||||
24 | #include "dbconfig.h" | 25 | #include "dbconfig.h" | ||
25 | #include "dbinitializer.h" | 26 | #include "dbinitializer.h" | ||
26 | #include "dbupdater.h" | 27 | #include "dbupdater.h" | ||
27 | #include "notificationmanager.h" | 28 | #include "notificationmanager.h" | ||
28 | #include "tracer.h" | 29 | #include "tracer.h" | ||
29 | #include "transaction.h" | 30 | #include "transaction.h" | ||
30 | #include "selectquerybuilder.h" | 31 | #include "selectquerybuilder.h" | ||
31 | #include "handlerhelper.h" | 32 | #include "handlerhelper.h" | ||
Show All 28 Lines | |||||
60 | #include <functional> | 61 | #include <functional> | ||
61 | 62 | | |||
62 | using namespace Akonadi; | 63 | using namespace Akonadi; | ||
63 | using namespace Akonadi::Server; | 64 | using namespace Akonadi::Server; | ||
64 | 65 | | |||
65 | static QMutex sTransactionMutex; | 66 | static QMutex sTransactionMutex; | ||
66 | bool DataStore::s_hasForeignKeyConstraints = false; | 67 | bool DataStore::s_hasForeignKeyConstraints = false; | ||
67 | 68 | | |||
68 | QThreadStorage<DataStore *> DataStore::sInstances; | 69 | static QThreadStorage<DataStore *> sInstances; | ||
dvratil: Not related, keep it in `DataStore`, please. | |||||
69 | 70 | | |||
70 | #define TRANSACTION_MUTEX_LOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.lock() | 71 | #define TRANSACTION_MUTEX_LOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.lock() | ||
71 | #define TRANSACTION_MUTEX_UNLOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.unlock() | 72 | #define TRANSACTION_MUTEX_UNLOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.unlock() | ||
72 | 73 | | |||
73 | #define setBoolPtr(ptr, val) \ | 74 | #define setBoolPtr(ptr, val) \ | ||
74 | { \ | 75 | { \ | ||
75 | if ((ptr)) { \ | 76 | if ((ptr)) { \ | ||
76 | *(ptr) = (val); \ | 77 | *(ptr) = (val); \ | ||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Line(s) | 172 | if (inTransaction()) { | |||
173 | // and rollback the outermost transaction. | 174 | // and rollback the outermost transaction. | ||
174 | m_transactionLevel = 1; | 175 | m_transactionLevel = 1; | ||
175 | rollbackTransaction(); | 176 | rollbackTransaction(); | ||
176 | } | 177 | } | ||
177 | 178 | | |||
178 | QueryCache::clear(); | 179 | QueryCache::clear(); | ||
179 | m_database.close(); | 180 | m_database.close(); | ||
180 | m_database = QSqlDatabase(); | 181 | m_database = QSqlDatabase(); | ||
181 | m_transactionQueries.clear(); | | |||
182 | QSqlDatabase::removeDatabase(m_connectionName); | 182 | QSqlDatabase::removeDatabase(m_connectionName); | ||
183 | 183 | | |||
184 | StorageDebugger::instance()->removeConnection(reinterpret_cast<qint64>(this)); | 184 | StorageDebugger::instance()->removeConnection(reinterpret_cast<qint64>(this)); | ||
185 | 185 | | |||
186 | m_dbOpened = false; | 186 | m_dbOpened = false; | ||
187 | } | 187 | } | ||
188 | 188 | | |||
189 | bool DataStore::init() | 189 | bool DataStore::init() | ||
▲ Show 20 Lines • Show All 1166 Lines • ▼ Show 20 Line(s) | |||||
1356 | } | 1356 | } | ||
1357 | 1357 | | |||
1358 | // static | 1358 | // static | ||
1359 | QDateTime DataStore::dateTimeToQDateTime(const QByteArray &dateTime) | 1359 | QDateTime DataStore::dateTimeToQDateTime(const QByteArray &dateTime) | ||
1360 | { | 1360 | { | ||
1361 | return QDateTime::fromString(QString::fromLatin1(dateTime), QStringLiteral("yyyy-MM-dd hh:mm:ss")); | 1361 | return QDateTime::fromString(QString::fromLatin1(dateTime), QStringLiteral("yyyy-MM-dd hh:mm:ss")); | ||
1362 | } | 1362 | } | ||
1363 | 1363 | | |||
1364 | void DataStore::addQueryToTransaction(const QString &statement, const QVector<QVariant> &bindValues, bool isBatch) | 1364 | bool DataStore::doRollback() | ||
1365 | { | 1365 | { | ||
1366 | // This is used for replaying deadlocked transactions, so only record queries | 1366 | QSqlDriver *driver = m_database.driver(); | ||
1367 | // for backends that support concurrent transactions. | | |||
1368 | if (!inTransaction() || DbType::isSystemSQLite(m_database)) { | | |||
1369 | return; | | |||
1370 | } | | |||
1371 | | ||||
1372 | m_transactionQueries.append({ statement, bindValues, isBatch }); | | |||
1373 | } | | |||
1374 | | ||||
1375 | QSqlQuery DataStore::retryLastTransaction(bool rollbackFirst) | | |||
1376 | { | | |||
1377 | if (!inTransaction() || DbType::isSystemSQLite(m_database)) { | | |||
1378 | return QSqlQuery(); | | |||
1379 | } | | |||
1380 | | ||||
1381 | if (rollbackFirst) { | | |||
1382 | // In some cases the SQL database won't rollback the failed transaction, so | | |||
1383 | // we need to do it manually | | |||
1384 | QElapsedTimer timer; timer.start(); | 1367 | QElapsedTimer timer; timer.start(); | ||
1385 | m_database.driver()->rollbackTransaction(); | 1368 | driver->rollbackTransaction(); | ||
1386 | StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), | 1369 | StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), | ||
1387 | false, timer.elapsed(), | 1370 | false, timer.elapsed(), | ||
1388 | m_database.lastError().text()); | 1371 | m_database.lastError().text()); | ||
1372 | if (m_database.lastError().isValid()) { | ||||
1373 | TRANSACTION_MUTEX_UNLOCK; | ||||
1374 | debugLastDbError("DataStore::rollbackTransaction"); | ||||
1375 | return false; | ||||
1389 | } | 1376 | } | ||
1390 | 1377 | TRANSACTION_MUTEX_UNLOCK; | |||
1391 | // The database has rolled back the actual transaction, so reset the counter | 1378 | return true; | ||
1392 | // to 0 and start a new one in beginTransaction(). Then restore the level | | |||
1393 | // because this has to be completely transparent to the original caller | | |||
1394 | const int oldTransactionLevel = m_transactionLevel; | | |||
1395 | m_transactionLevel = 0; | | |||
1396 | if (!beginTransaction(QStringLiteral("RETRY LAST TRX"))) { | | |||
1397 | m_transactionLevel = oldTransactionLevel; | | |||
1398 | return QSqlQuery(); | | |||
1399 | } | | |||
1400 | m_transactionLevel = oldTransactionLevel; | | |||
1401 | | ||||
1402 | QSqlQuery lastQuery; | | |||
1403 | for (auto q = m_transactionQueries.begin(), qEnd = m_transactionQueries.end(); q != qEnd; ++q) { | | |||
1404 | QSqlQuery query(database()); | | |||
1405 | query.prepare(q->query); | | |||
1406 | for (int i = 0, total = q->boundValues.count(); i < total; ++i) { | | |||
1407 | query.bindValue(QLatin1Char(':') + QString::number(i), q->boundValues.at(i)); | | |||
1408 | } | | |||
1409 | | ||||
1410 | bool res = false; | | |||
1411 | QElapsedTimer t; t.start(); | | |||
1412 | if (q->isBatch) { | | |||
1413 | res = query.execBatch(); | | |||
1414 | } else { | | |||
1415 | res = query.exec(); | | |||
1416 | } | | |||
1417 | if (StorageDebugger::instance()->isSQLDebuggingEnabled()) { | | |||
1418 | Q_EMIT StorageDebugger::instance()->queryExecuted(reinterpret_cast<qint64>(this), | | |||
1419 | query, t.elapsed()); | | |||
1420 | } else { | | |||
1421 | StorageDebugger::instance()->incSequence(); | | |||
1422 | } | | |||
1423 | | ||||
1424 | if (!res) { | | |||
1425 | // Don't do another deadlock detection here, just give up. | | |||
1426 | qCCritical(AKONADISERVER_LOG) << "DATABASE ERROR when retrying transaction"; | | |||
1427 | qCCritical(AKONADISERVER_LOG) << " Error code:" << query.lastError().nativeErrorCode(); | | |||
1428 | qCCritical(AKONADISERVER_LOG) << " DB error: " << query.lastError().databaseText(); | | |||
1429 | qCCritical(AKONADISERVER_LOG) << " Error text:" << query.lastError().text(); | | |||
1430 | qCCritical(AKONADISERVER_LOG) << " Query:" << query.executedQuery(); | | |||
1431 | | ||||
1432 | // Return the last query, because that's what caller expects to retrieve | | |||
1433 | // from QueryBuilder. It is in error state anyway. | | |||
1434 | return query; | | |||
1435 | } | | |||
1436 | | ||||
1437 | lastQuery = query; | | |||
1438 | } | 1379 | } | ||
1439 | 1380 | | |||
1440 | return lastQuery; | 1381 | void DataStore::transactionKilledByDB() | ||
1382 | { | ||||
1383 | m_transactionKilledByDB = true; | ||||
1384 | cleanupAfterRollback(); | ||||
1385 | Q_EMIT transactionRolledBack(); | ||||
1441 | } | 1386 | } | ||
1442 | 1387 | | |||
1443 | bool DataStore::beginTransaction(const QString &name) | 1388 | bool DataStore::beginTransaction(const QString &name) | ||
1444 | { | 1389 | { | ||
1445 | if (!m_dbOpened) { | 1390 | if (!m_dbOpened) { | ||
1446 | return false; | 1391 | return false; | ||
1447 | } | 1392 | } | ||
1448 | 1393 | | |||
1449 | if (m_transactionLevel == 0) { | 1394 | if (m_transactionLevel == 0 || m_transactionKilledByDB) { | ||
1395 | m_transactionKilledByDB = false; | ||||
1450 | QElapsedTimer timer; | 1396 | QElapsedTimer timer; | ||
1451 | timer.start(); | 1397 | timer.start(); | ||
1452 | TRANSACTION_MUTEX_LOCK; | 1398 | TRANSACTION_MUTEX_LOCK; | ||
1453 | if (DbType::type(m_database) == DbType::Sqlite) { | 1399 | if (DbType::type(m_database) == DbType::Sqlite) { | ||
1454 | m_database.exec(QStringLiteral("BEGIN IMMEDIATE TRANSACTION")); | 1400 | m_database.exec(QStringLiteral("BEGIN IMMEDIATE TRANSACTION")); | ||
1455 | StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), | 1401 | StorageDebugger::instance()->addTransaction(reinterpret_cast<qint64>(this), | ||
1456 | name, timer.elapsed(), | 1402 | name, timer.elapsed(), | ||
1457 | m_database.lastError().text()); | 1403 | m_database.lastError().text()); | ||
Show All 36 Lines | 1436 | { | |||
1494 | 1440 | | |||
1495 | if (m_transactionLevel == 0) { | 1441 | if (m_transactionLevel == 0) { | ||
1496 | qCWarning(AKONADISERVER_LOG) << "DataStore::rollbackTransaction(): No transaction in progress!"; | 1442 | qCWarning(AKONADISERVER_LOG) << "DataStore::rollbackTransaction(): No transaction in progress!"; | ||
1497 | return false; | 1443 | return false; | ||
1498 | } | 1444 | } | ||
1499 | 1445 | | |||
1500 | --m_transactionLevel; | 1446 | --m_transactionLevel; | ||
1501 | 1447 | | |||
1502 | if (m_transactionLevel == 0) { | 1448 | if (m_transactionLevel == 0 && !m_transactionKilledByDB) { | ||
1503 | QSqlDriver *driver = m_database.driver(); | 1449 | doRollback(); | ||
1450 | cleanupAfterRollback(); | ||||
1504 | Q_EMIT transactionRolledBack(); | 1451 | Q_EMIT transactionRolledBack(); | ||
1505 | QElapsedTimer timer; timer.start(); | | |||
1506 | driver->rollbackTransaction(); | | |||
1507 | StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), | | |||
1508 | false, timer.elapsed(), | | |||
1509 | m_database.lastError().text()); | | |||
1510 | if (m_database.lastError().isValid()) { | | |||
1511 | TRANSACTION_MUTEX_UNLOCK; | | |||
1512 | debugLastDbError("DataStore::rollbackTransaction"); | | |||
1513 | return false; | | |||
1514 | } | | |||
1515 | TRANSACTION_MUTEX_UNLOCK; | | |||
1516 | | ||||
1517 | m_transactionQueries.clear(); | | |||
1518 | } | 1452 | } | ||
1519 | 1453 | | |||
1520 | return true; | 1454 | return true; | ||
1521 | } | 1455 | } | ||
1522 | 1456 | | |||
1523 | bool DataStore::commitTransaction() | 1457 | bool DataStore::commitTransaction() | ||
1524 | { | 1458 | { | ||
1525 | if (!m_dbOpened) { | 1459 | if (!m_dbOpened) { | ||
1526 | return false; | 1460 | return false; | ||
1527 | } | 1461 | } | ||
1528 | 1462 | | |||
1529 | if (m_transactionLevel == 0) { | 1463 | if (m_transactionLevel == 0) { | ||
1530 | qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): No transaction in progress!"; | 1464 | qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): No transaction in progress!"; | ||
1531 | return false; | 1465 | return false; | ||
1532 | } | 1466 | } | ||
1533 | 1467 | | |||
1534 | if (m_transactionLevel == 1) { | 1468 | if (m_transactionLevel == 1) { | ||
1469 | if (m_transactionKilledByDB) { | ||||
1470 | qCWarning(AKONADISERVER_LOG) << "DataStore::commitTransaction(): Cannot commit, transaction was killed by mysql deadlock handling!"; | ||||
1471 | return false; | ||||
1472 | } | ||||
1535 | QSqlDriver *driver = m_database.driver(); | 1473 | QSqlDriver *driver = m_database.driver(); | ||
1536 | QElapsedTimer timer; | 1474 | QElapsedTimer timer; | ||
1537 | timer.start(); | 1475 | timer.start(); | ||
1538 | driver->commitTransaction(); | 1476 | driver->commitTransaction(); | ||
1539 | StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), | 1477 | StorageDebugger::instance()->removeTransaction(reinterpret_cast<qint64>(this), | ||
1540 | true, timer.elapsed(), | 1478 | true, timer.elapsed(), | ||
1541 | m_database.lastError().text()); | 1479 | m_database.lastError().text()); | ||
1542 | if (m_database.lastError().isValid()) { | 1480 | if (m_database.lastError().isValid()) { | ||
1543 | debugLastDbError("DataStore::commitTransaction"); | 1481 | debugLastDbError("DataStore::commitTransaction"); | ||
1544 | rollbackTransaction(); | 1482 | rollbackTransaction(); | ||
1545 | return false; | 1483 | return false; | ||
1546 | } else { | 1484 | } else { | ||
1547 | TRANSACTION_MUTEX_UNLOCK; | 1485 | TRANSACTION_MUTEX_UNLOCK; | ||
1548 | m_transactionLevel--; | 1486 | m_transactionLevel--; | ||
1549 | Q_EMIT transactionCommitted(); | 1487 | Q_EMIT transactionCommitted(); | ||
1550 | } | 1488 | } | ||
1551 | | ||||
1552 | m_transactionQueries.clear(); | | |||
1553 | } else { | 1489 | } else { | ||
1554 | m_transactionLevel--; | 1490 | m_transactionLevel--; | ||
1555 | } | 1491 | } | ||
1556 | return true; | 1492 | return true; | ||
1557 | } | 1493 | } | ||
1558 | 1494 | | |||
1559 | bool DataStore::inTransaction() const | 1495 | bool DataStore::inTransaction() const | ||
1560 | { | 1496 | { | ||
1561 | return m_transactionLevel > 0; | 1497 | return m_transactionLevel > 0; | ||
1562 | } | 1498 | } | ||
1563 | 1499 | | |||
1564 | void DataStore::sendKeepAliveQuery() | 1500 | void DataStore::sendKeepAliveQuery() | ||
1565 | { | 1501 | { | ||
1566 | if (m_database.isOpen()) { | 1502 | if (m_database.isOpen()) { | ||
1567 | QSqlQuery query(m_database); | 1503 | QSqlQuery query(m_database); | ||
1568 | query.exec(QStringLiteral("SELECT 1")); | 1504 | query.exec(QStringLiteral("SELECT 1")); | ||
1569 | } | 1505 | } | ||
1570 | } | 1506 | } | ||
1507 | | ||||
1508 | void DataStore::cleanupAfterRollback() | ||||
1509 | { | ||||
1510 | MimeType::invalidateCompleteCache(); | ||||
1511 | Flag::invalidateCompleteCache(); | ||||
1512 | Resource::invalidateCompleteCache(); | ||||
1513 | Collection::invalidateCompleteCache(); | ||||
1514 | PartType::invalidateCompleteCache(); | ||||
1515 | CollectionStatistics::self()->expireCache(); | ||||
1516 | QueryCache::clear(); | ||||
1517 | } |
Not related, keep it in DataStore, please.