mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-01-07 03:19:39 -06:00
handle lost connection during transactions
- bugfix reset transactions when lost connection occurs during commit()/rollBack()/savepoint()/rollBackToSavepoint() method calls - enhanced beginTransaction(), reconnect when connection was lost
This commit is contained in:
@@ -11,6 +11,8 @@ TINY_SYSTEM_HEADER
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
class QSqlError;
|
||||
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
@@ -35,6 +37,8 @@ namespace Concerns
|
||||
|
||||
/*! Determine if the given exception was caused by a lost connection. */
|
||||
bool causedByLostConnection(const Exceptions::SqlError &e) const;
|
||||
/*! Determine if the given exception was caused by a lost connection. */
|
||||
bool causedByLostConnection(const QSqlError &e) const;
|
||||
};
|
||||
|
||||
} // namespace Concerns
|
||||
|
||||
@@ -12,6 +12,8 @@ TINY_SYSTEM_HEADER
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
class QSqlError;
|
||||
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
@@ -75,6 +77,19 @@ namespace Concerns
|
||||
/*! Dynamic cast *this to the Concerns::CountsQueries & base type. */
|
||||
Concerns::CountsQueries &countsQueries();
|
||||
|
||||
/*! Transform a QtSql transaction error to TinyORM SqlTransactionError
|
||||
exception. */
|
||||
void throwIfTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error);
|
||||
|
||||
/*! Handle an error returned when beginning a transaction. */
|
||||
void handleStartTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error);
|
||||
/*! Handle an error returned during a transaction commit, rollBack, savepoint or
|
||||
rollbackToSavepoint. */
|
||||
void handleCommonTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error);
|
||||
|
||||
/*! The connection is in the transaction state. */
|
||||
bool m_inTransaction = false;
|
||||
/*! Active savepoints counter. */
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace Schema
|
||||
const QString &queryString, const QVector<QVariant> &bindings,
|
||||
const RunCallback<Return> &callback) const;
|
||||
|
||||
/*! Reconnect to the database if a PDO connection is missing. */
|
||||
/*! Reconnect to the database if a Qt connection is missing. */
|
||||
void reconnectIfMissingConnection() const;
|
||||
|
||||
/*! The active QSqlDatabase connection name. */
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace Orm::Concerns
|
||||
{
|
||||
|
||||
bool DetectsLostConnections::causedByLostConnection(const Exceptions::SqlError &e) const
|
||||
{
|
||||
return causedByLostConnection(e.getSqlError().databaseText());
|
||||
}
|
||||
|
||||
bool DetectsLostConnections::causedByLostConnection(const QSqlError &e) const
|
||||
{
|
||||
// TODO verify this will be pain in the ass 😕 silverqx
|
||||
static const QVector<QString> lostMessagesCache {
|
||||
@@ -53,7 +58,7 @@ bool DetectsLostConnections::causedByLostConnection(const Exceptions::SqlError &
|
||||
};
|
||||
|
||||
return std::ranges::any_of(lostMessagesCache,
|
||||
[databaseError = e.getSqlError().databaseText()]
|
||||
[databaseError = e.databaseText()]
|
||||
(const auto &lostMessage)
|
||||
{
|
||||
// found
|
||||
|
||||
@@ -20,10 +20,11 @@ ManagesTransactions::ManagesTransactions()
|
||||
bool ManagesTransactions::beginTransaction()
|
||||
{
|
||||
Q_ASSERT(m_inTransaction == false);
|
||||
Q_ASSERT(m_savepoints == 0);
|
||||
|
||||
databaseConnection().reconnectIfMissingConnection();
|
||||
|
||||
static const auto query = QStringLiteral("START TRANSACTION");
|
||||
static const auto queryString = QStringLiteral("START TRANSACTION");
|
||||
|
||||
// Elapsed timer needed
|
||||
const auto countElapsed = databaseConnection().shouldCountElapsed();
|
||||
@@ -35,10 +36,9 @@ bool ManagesTransactions::beginTransaction()
|
||||
if (!databaseConnection().pretending() &&
|
||||
!databaseConnection().getQtConnection().transaction()
|
||||
)
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(__tiny_func__, query),
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
handleStartTransactionError(
|
||||
__tiny_func__, queryString,
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
|
||||
m_inTransaction = true;
|
||||
|
||||
@@ -49,9 +49,9 @@ bool ManagesTransactions::beginTransaction()
|
||||
that it took to run and then log the query and execution time.
|
||||
We'll log time in milliseconds. */
|
||||
if (databaseConnection().pretending())
|
||||
databaseConnection().logTransactionQueryForPretend(query);
|
||||
databaseConnection().logTransactionQueryForPretend(queryString);
|
||||
else
|
||||
databaseConnection().logTransactionQuery(query, std::move(elapsed));
|
||||
databaseConnection().logTransactionQuery(queryString, std::move(elapsed));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ bool ManagesTransactions::commit()
|
||||
{
|
||||
Q_ASSERT(m_inTransaction);
|
||||
|
||||
static const auto query = QStringLiteral("COMMIT");
|
||||
static const auto queryString = QStringLiteral("COMMIT");
|
||||
|
||||
// Elapsed timer needed
|
||||
const auto countElapsed = databaseConnection().shouldCountElapsed();
|
||||
@@ -72,10 +72,8 @@ bool ManagesTransactions::commit()
|
||||
if (!databaseConnection().pretending() &&
|
||||
!databaseConnection().getQtConnection().commit()
|
||||
)
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(__tiny_func__, query),
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
handleCommonTransactionError(__tiny_func__, queryString,
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
|
||||
resetTransactions();
|
||||
|
||||
@@ -86,9 +84,9 @@ bool ManagesTransactions::commit()
|
||||
that it took to run and then log the query and execution time.
|
||||
We'll log time in milliseconds. */
|
||||
if (databaseConnection().pretending())
|
||||
databaseConnection().logTransactionQueryForPretend(query);
|
||||
databaseConnection().logTransactionQueryForPretend(queryString);
|
||||
else
|
||||
databaseConnection().logTransactionQuery(query, std::move(elapsed));
|
||||
databaseConnection().logTransactionQuery(queryString, std::move(elapsed));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -97,7 +95,7 @@ bool ManagesTransactions::rollBack()
|
||||
{
|
||||
Q_ASSERT(m_inTransaction);
|
||||
|
||||
static const auto query = QStringLiteral("ROLLBACK");
|
||||
static const auto queryString = QStringLiteral("ROLLBACK");
|
||||
|
||||
// Elapsed timer needed
|
||||
const auto countElapsed = databaseConnection().shouldCountElapsed();
|
||||
@@ -109,10 +107,8 @@ bool ManagesTransactions::rollBack()
|
||||
if (!databaseConnection().pretending() &&
|
||||
!databaseConnection().getQtConnection().rollback()
|
||||
)
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(__tiny_func__, query),
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
handleCommonTransactionError(__tiny_func__, queryString,
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
|
||||
resetTransactions();
|
||||
|
||||
@@ -123,20 +119,20 @@ bool ManagesTransactions::rollBack()
|
||||
that it took to run and then log the query and execution time.
|
||||
We'll log time in milliseconds. */
|
||||
if (databaseConnection().pretending())
|
||||
databaseConnection().logTransactionQueryForPretend(query);
|
||||
databaseConnection().logTransactionQueryForPretend(queryString);
|
||||
else
|
||||
databaseConnection().logTransactionQuery(query, std::move(elapsed));
|
||||
databaseConnection().logTransactionQuery(queryString, std::move(elapsed));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManagesTransactions::savepoint(const QString &id)
|
||||
{
|
||||
// TODO rewrite savepoint() and rollBack() with a new m_connection.statement() API silverqx
|
||||
Q_ASSERT(m_inTransaction);
|
||||
|
||||
auto savePoint = databaseConnection().getQtQuery();
|
||||
const auto query = QStringLiteral("SAVEPOINT %1_%2").arg(m_savepointNamespace, id);
|
||||
const auto queryString =
|
||||
QStringLiteral("SAVEPOINT %1_%2").arg(m_savepointNamespace, id);
|
||||
|
||||
// Elapsed timer needed
|
||||
const auto countElapsed = databaseConnection().shouldCountElapsed();
|
||||
@@ -146,11 +142,9 @@ bool ManagesTransactions::savepoint(const QString &id)
|
||||
timer.start();
|
||||
|
||||
// Execute a savepoint query
|
||||
if (!databaseConnection().pretending() && !savePoint.exec(query))
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(__tiny_func__, query),
|
||||
savePoint.lastError());
|
||||
if (!databaseConnection().pretending() && !savePoint.exec(queryString))
|
||||
handleCommonTransactionError(__tiny_func__, queryString,
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
|
||||
++m_savepoints;
|
||||
|
||||
@@ -161,9 +155,9 @@ bool ManagesTransactions::savepoint(const QString &id)
|
||||
that it took to run and then log the query and execution time.
|
||||
We'll log time in milliseconds. */
|
||||
if (databaseConnection().pretending())
|
||||
databaseConnection().logTransactionQueryForPretend(query);
|
||||
databaseConnection().logTransactionQueryForPretend(queryString);
|
||||
else
|
||||
databaseConnection().logTransactionQuery(query, std::move(elapsed));
|
||||
databaseConnection().logTransactionQuery(queryString, std::move(elapsed));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -179,8 +173,8 @@ bool ManagesTransactions::rollbackToSavepoint(const QString &id)
|
||||
Q_ASSERT(m_savepoints > 0);
|
||||
|
||||
auto rollbackToSavepoint = databaseConnection().getQtQuery();
|
||||
const auto query = QStringLiteral("ROLLBACK TO SAVEPOINT %1_%2")
|
||||
.arg(m_savepointNamespace, id);
|
||||
const auto queryString =
|
||||
QStringLiteral("ROLLBACK TO SAVEPOINT %1_%2").arg(m_savepointNamespace, id);
|
||||
|
||||
// Elapsed timer needed
|
||||
const auto countElapsed = databaseConnection().shouldCountElapsed();
|
||||
@@ -190,11 +184,9 @@ bool ManagesTransactions::rollbackToSavepoint(const QString &id)
|
||||
timer.start();
|
||||
|
||||
// Execute a rollback to savepoint query
|
||||
if (!databaseConnection().pretending() && !rollbackToSavepoint.exec(query))
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(__tiny_func__, query),
|
||||
rollbackToSavepoint.lastError());
|
||||
if (!databaseConnection().pretending() && !rollbackToSavepoint.exec(queryString))
|
||||
handleCommonTransactionError(__tiny_func__, queryString,
|
||||
databaseConnection().getRawQtConnection().lastError());
|
||||
|
||||
m_savepoints = std::max<std::size_t>(0, m_savepoints - 1);
|
||||
|
||||
@@ -205,9 +197,9 @@ bool ManagesTransactions::rollbackToSavepoint(const QString &id)
|
||||
that it took to run and then log the query and execution time.
|
||||
We'll log time in milliseconds. */
|
||||
if (databaseConnection().pretending())
|
||||
databaseConnection().logTransactionQueryForPretend(query);
|
||||
databaseConnection().logTransactionQueryForPretend(queryString);
|
||||
else
|
||||
databaseConnection().logTransactionQuery(query, std::move(elapsed));
|
||||
databaseConnection().logTransactionQuery(queryString, std::move(elapsed));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -245,6 +237,35 @@ CountsQueries &ManagesTransactions::countsQueries()
|
||||
return dynamic_cast<CountsQueries &>(*this);
|
||||
}
|
||||
|
||||
void ManagesTransactions::throwIfTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error)
|
||||
{
|
||||
throw Exceptions::SqlTransactionError(
|
||||
QStringLiteral("Statement in %1() failed : %2")
|
||||
.arg(functionName, queryString),
|
||||
error);
|
||||
}
|
||||
|
||||
void ManagesTransactions::handleStartTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error)
|
||||
{
|
||||
if (!databaseConnection().causedByLostConnection(error))
|
||||
throwIfTransactionError(std::move(functionName), queryString, std::move(error));
|
||||
|
||||
databaseConnection().reconnect();
|
||||
|
||||
databaseConnection().getQtConnection().transaction();
|
||||
}
|
||||
|
||||
void ManagesTransactions::handleCommonTransactionError(
|
||||
QString &&functionName, const QString &queryString, QSqlError &&error)
|
||||
{
|
||||
if (databaseConnection().causedByLostConnection(error))
|
||||
resetTransactions();
|
||||
|
||||
throwIfTransactionError(std::move(functionName), queryString, std::move(error));
|
||||
}
|
||||
|
||||
} // namespace Orm::Concerns
|
||||
|
||||
TINYORM_END_COMMON_NAMESPACE
|
||||
|
||||
@@ -280,7 +280,7 @@ DatabaseConnection::setQtConnectionResolver(
|
||||
/* m_qtConnection.reset() is called also in DatabaseConnection::disconnect(),
|
||||
because both methods are public apis.
|
||||
m_qtConnection can also be understood as m_qtConnectionWasResolved,
|
||||
because it performs two functions, saves active connection name and
|
||||
because it has two functions, saves active connection name and
|
||||
if it's not nullopt, then it means, that the database connection was
|
||||
resolved by m_qtConnectionResolver.
|
||||
If it's nullopt, then m_qtConnectionResolver should be called to
|
||||
@@ -373,7 +373,7 @@ void DatabaseConnection::disconnect()
|
||||
and invalidating any existing QSqlQuery objects that are used
|
||||
with the database.
|
||||
Only close the QSqlDatabase database connection and don't remove it
|
||||
from QSqlDatabase connection repository, so they can be reused, it's
|
||||
from QSqlDatabase connection repository, so it can be reused, it's
|
||||
better for performance. */
|
||||
getRawQtConnection().close();
|
||||
|
||||
@@ -492,12 +492,19 @@ std::unique_ptr<QueryProcessor> DatabaseConnection::getDefaultPostProcessor() co
|
||||
|
||||
void DatabaseConnection::reconnectIfMissingConnection() const
|
||||
{
|
||||
if (!m_qtConnectionResolver) {
|
||||
// This should never happen, but when it does, I want to know about that
|
||||
Q_ASSERT(m_qtConnection);
|
||||
/* Calls a connection resolver defined
|
||||
in the ConnectionFactory::createQSqlDatabaseResolver(), the connection resolver
|
||||
is passed to the DatabaseConnection constructor and is always available. Only
|
||||
one exception is when disconnect() is called, it resets connection resolver which
|
||||
will be recreated (with the db connection of course) here, that is only one case
|
||||
when code below (reconnect() logic) is true as I'm aware of.*/
|
||||
if (m_qtConnectionResolver)
|
||||
return;
|
||||
|
||||
reconnect();
|
||||
}
|
||||
// This should never happen, but when it does, I want to know about that
|
||||
Q_ASSERT(m_qtConnection);
|
||||
|
||||
reconnect();
|
||||
}
|
||||
|
||||
/* private */
|
||||
|
||||
Reference in New Issue
Block a user