diff --git a/src/mysql/Database.cpp b/src/mysql/Database.cpp index b1c7711..4a1e529 100644 --- a/src/mysql/Database.cpp +++ b/src/mysql/Database.cpp @@ -4,7 +4,9 @@ #include #include #include +#include "mysqld_error.h" #include "../lua/LuaObject.h" +#include "errmsg.h" Database::Database(std::string host, std::string username, std::string pw, std::string database, unsigned int port, std::string unixSocket) : @@ -30,32 +32,38 @@ Database::~Database() { //This makes sure that all stmts always get freed -void Database::cacheStatement(MYSQL_STMT *stmt) { - if (stmt == nullptr) return; +std::shared_ptr Database::cacheStatement(MYSQL_STMT *stmt) { + if (stmt == nullptr) return std::make_shared(nullptr, false); std::unique_lock lock(m_statementMutex); - cachedStatements.insert(stmt); + auto handle = std::make_shared(stmt, true); + cachedStatements.insert(handle); + return handle; } //This notifies the database thread to free this statement some time in the future -void Database::freeStatement(MYSQL_STMT *stmt) { - if (stmt == nullptr) return; +void Database::freeStatement(const std::shared_ptr &handle) { + if (handle == nullptr || !handle->isValid()) return; std::unique_lock lock(m_statementMutex); - if (cachedStatements.find(stmt) != cachedStatements.end()) { + if (cachedStatements.find(handle) != cachedStatements.end()) { //Otherwise, the statement was already freed - cachedStatements.erase(stmt); - freedStatements.insert(stmt); + cachedStatements.erase(handle); + freedStatements.insert(handle->stmt); } + handle->invalidate(); } //Frees all statements that were allocated by the database -//This is called when the database shuts down +//This is called when the database shuts down or a reconnect happens void Database::freeCachedStatements() { std::unique_lock lock(m_statementMutex); - for (auto &stmt: cachedStatements) { - mysql_stmt_close(stmt); + for (auto &handle: cachedStatements) { + if (handle == nullptr || !handle->isValid()) continue; + mysql_stmt_close(handle->stmt); + handle->invalidate(); } cachedStatements.clear(); for (auto &stmt: freedStatements) { + if (stmt == nullptr) continue; mysql_stmt_close(stmt); } freedStatements.clear(); @@ -66,6 +74,7 @@ void Database::freeCachedStatements() { void Database::freeUnusedStatements() { std::unique_lock lock(m_statementMutex); for (auto &stmt: freedStatements) { + //Even if this returns an error, the handle will be freed mysql_stmt_close(stmt); } freedStatements.clear(); @@ -299,21 +308,6 @@ void Database::setCachePreparedStatements(bool shouldCache) { cachePreparedStatements = shouldCache; } -//Should only be called from the db thread -//While the mysql documentation says that mysql_options should only be called -//before the connection is done it appears to work after just fine (at least for reconnect) -void Database::setSQLAutoReconnect(bool shouldReconnect) { - auto myAutoReconnectBool = (my_bool) shouldReconnect; - mysql_optionsv(m_sql, MYSQL_OPT_RECONNECT, &myAutoReconnectBool); -} - -//Should only be called from the db thread -bool Database::getSQLAutoReconnect() { - my_bool autoReconnect; - mysql_get_optionv(m_sql, MYSQL_OPT_RECONNECT, &autoReconnect); - return (bool) autoReconnect; -} - void Database::failWaitingQuery(const std::shared_ptr &query, const std::shared_ptr &data, std::string reason) { data->setError(std::move(reason)); @@ -381,9 +375,6 @@ void Database::connectRun() { return; } this->customSSLSettings.applySSLSettings(this->m_sql); - if (this->shouldAutoReconnect) { - setSQLAutoReconnect(true); - } const char *socketStr = (this->socket.length() == 0) ? nullptr : this->socket.c_str(); unsigned long clientFlag = (this->useMultiStatements) ? CLIENT_MULTI_STATEMENTS : 0; clientFlag |= CLIENT_MULTI_RESULTS; @@ -415,6 +406,27 @@ void Database::connectRun() { } } +void Database::runQuery(const std::shared_ptr& query, const std::shared_ptr& data, bool retry) { + try { + query->executeStatement(*this, this->m_sql, data); + data->setResultStatus(QUERY_SUCCESS); + } catch (const MySQLException &error) { + unsigned int errorCode = error.getErrorCode(); + bool retryableError = errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR || + errorCode == ER_MAX_PREPARED_STMT_COUNT_REACHED || errorCode == ER_UNKNOWN_STMT_HANDLER || + errorCode == CR_NO_PREPARE_STMT; + if (retry && retryableError && attemptReconnect()) { + //Need to free statements before retrying in case the connection was lost + //and prepared statement handles have become invalid + freeCachedStatements(); + runQuery(query, data, false); + } else { + data->setResultStatus(QUERY_ERROR); + data->setError(error.what()); + } + } +} + /* The run method of the thread of the database instance. */ void Database::run() { @@ -432,7 +444,9 @@ void Database::run() { { //New scope so mutex will be released as soon as possible std::unique_lock queryMutex(m_queryMutex); - curQuery->executeStatement(*this, this->m_sql, data); + data->setStatus(QUERY_RUNNING); + runQuery(curQuery, data, this->shouldAutoReconnect); + data->setStatus(QUERY_COMPLETE); } finishedQueries.put(pair); { @@ -449,4 +463,16 @@ void Database::run() { //So that statements get eventually freed even if the queue is constantly full freeUnusedStatements(); } -} \ No newline at end of file +} + +bool Database::attemptReconnect() { + bool success; + my_bool reconnect = '1'; + mysql_optionsv(this->m_sql, MYSQL_OPT_RECONNECT, &reconnect); + success = mariadb_reconnect(this->m_sql) == 0; + reconnect = '0'; + mysql_optionsv(this->m_sql, MYSQL_OPT_RECONNECT, &reconnect); + return success; +} + +StatementHandle::StatementHandle(MYSQL_STMT *stmt, bool valid) : stmt(stmt), valid(valid) {} diff --git a/src/mysql/Database.h b/src/mysql/Database.h index 93ab4dc..da6ea5e 100644 --- a/src/mysql/Database.h +++ b/src/mysql/Database.h @@ -9,6 +9,7 @@ #include #include #include +#include "StatementHandle.h" #include #include #include "GarrysMod/Lua/Interface.h" @@ -36,6 +37,7 @@ enum DatabaseStatus { DATABASE_CONNECTION_FAILED = 3 }; + class Database : public std::enable_shared_from_this { friend class IQuery; @@ -47,9 +49,9 @@ class Database : public std::enable_shared_from_this { ~Database(); - void cacheStatement(MYSQL_STMT *stmt); + std::shared_ptr cacheStatement(MYSQL_STMT *stmt); - void freeStatement(MYSQL_STMT *stmt); + void freeStatement(const std::shared_ptr &handle); void enqueueQuery(const std::shared_ptr &query, const std::shared_ptr &data); @@ -99,17 +101,14 @@ class Database : public std::enable_shared_from_this { bool connectionSuccessful() { return m_success; } + bool attemptReconnect(); + std::string connectionError() { return m_connection_err; } std::deque, std::shared_ptr>> takeFinishedQueries() { return finishedQueries.clear(); } - void setSQLAutoReconnect(bool autoReconnect); - - bool getSQLAutoReconnect(); - - private: Database(std::string host, std::string username, std::string pw, std::string database, unsigned int port, std::string unixSocket); @@ -122,6 +121,8 @@ class Database : public std::enable_shared_from_this { void run(); + void runQuery(const std::shared_ptr &query, const std::shared_ptr &data, bool retry); + void connectRun(); void abortWaitingQuery(); @@ -133,7 +134,7 @@ class Database : public std::enable_shared_from_this { BlockingQueue, std::shared_ptr>> finishedQueries{}; BlockingQueue, std::shared_ptr>> queryQueue{}; - std::unordered_set cachedStatements{}; + std::unordered_set> cachedStatements{}; std::unordered_set freedStatements{}; MYSQL *m_sql = nullptr; std::thread m_thread; diff --git a/src/mysql/IQuery.cpp b/src/mysql/IQuery.cpp index 9a79879..5ec5aea 100644 --- a/src/mysql/IQuery.cpp +++ b/src/mysql/IQuery.cpp @@ -29,15 +29,6 @@ QueryResultStatus IQuery::getResultStatus() const { //Wrapper for c api calls //Just throws an exception if anything goes wrong for ease of use -void IQuery::mysqlAutocommit(MYSQL *sql, bool auto_mode) { - my_bool result = mysql_autocommit(sql, (my_bool) auto_mode); - if (result) { - const char *errorMessage = mysql_error(sql); - unsigned int errorCode = mysql_errno(sql); - throw MySQLException(errorCode, errorMessage); - } -} - //Returns if the query has been queued with the database instance bool IQuery::isRunning() { return !runningQueryData.empty(); diff --git a/src/mysql/IQuery.h b/src/mysql/IQuery.h index ebcf25f..8cbf218 100644 --- a/src/mysql/IQuery.h +++ b/src/mysql/IQuery.h @@ -84,13 +84,11 @@ class IQuery : public std::enable_shared_from_this { std::shared_ptr callbackQueryData; protected: - virtual bool executeStatement(Database &database, MYSQL *m_sql, std::shared_ptr data) = 0; + virtual void executeStatement(Database &database, MYSQL *m_sql, const std::shared_ptr& data) = 0; //Wrapper functions for c api that throw exceptions static void mysqlQuery(MYSQL *sql, std::string &query); - static void mysqlAutocommit(MYSQL *sql, bool auto_mode); - static MYSQL_RES *mysqlStoreResults(MYSQL *sql); static bool mysqlNextResult(MYSQL *sql); diff --git a/src/mysql/PingQuery.cpp b/src/mysql/PingQuery.cpp index ee35b3d..615b9fb 100644 --- a/src/mysql/PingQuery.cpp +++ b/src/mysql/PingQuery.cpp @@ -15,9 +15,6 @@ PingQuery::~PingQuery() = default; /* Executes the ping query */ -void PingQuery::executeQuery(Database &database, MYSQL *connection, const std::shared_ptr &data) { - bool oldAutoReconnect = database.getSQLAutoReconnect(); - database.setSQLAutoReconnect(true); - this->pingSuccess = mysql_ping(connection) == 0; - database.setSQLAutoReconnect(oldAutoReconnect); +void PingQuery::executeStatement(Database &database, MYSQL *connection, const std::shared_ptr &data) { + this->pingSuccess = mysql_ping(connection) == 0 || database.attemptReconnect(); } \ No newline at end of file diff --git a/src/mysql/PingQuery.h b/src/mysql/PingQuery.h index 05d4e3d..3fbbff9 100644 --- a/src/mysql/PingQuery.h +++ b/src/mysql/PingQuery.h @@ -14,7 +14,7 @@ class PingQuery : public Query { ~PingQuery() override; protected: explicit PingQuery(const std::shared_ptr& dbase); - void executeQuery(Database &database, MYSQL* m_sql, const std::shared_ptr &data) override; + void executeStatement(Database &database, MYSQL* m_sql, const std::shared_ptr &data) override; bool pingSuccess = false; }; #endif \ No newline at end of file diff --git a/src/mysql/PreparedQuery.cpp b/src/mysql/PreparedQuery.cpp index 2857883..9772ff8 100644 --- a/src/mysql/PreparedQuery.cpp +++ b/src/mysql/PreparedQuery.cpp @@ -173,17 +173,13 @@ void PreparedQuery::generateMysqlBinds(MYSQL_BIND *binds, } } - /* Executes the prepared query * This function can only ever return one result set * Note: If an error occurs at the nth query all the actions done before * that nth query won't be reverted even though this query results in an error */ -void PreparedQuery::executeQuery(Database &database, MYSQL *connection, const std::shared_ptr &ptr) { +void PreparedQuery::executeStatement(Database &database, MYSQL *connection, const std::shared_ptr& ptr) { std::shared_ptr data = std::dynamic_pointer_cast(ptr); - bool shouldReconnect = database.getSQLAutoReconnect(); - //Autoreconnect has to be disabled for prepared statement since prepared statements - //get reset on the server if the connection fails and auto reconnects try { MYSQL_STMT *stmt = nullptr; auto stmtClose = finally([&] { @@ -191,16 +187,15 @@ void PreparedQuery::executeQuery(Database &database, MYSQL *connection, const st mysql_stmt_close(stmt); } }); - if (this->cachedStatement.load() != nullptr) { - stmt = this->cachedStatement; + if (this->cachedStatement != nullptr && this->cachedStatement->isValid()) { + stmt = this->cachedStatement->stmt; } else { stmt = mysqlStmtInit(connection); my_bool attrMaxLength = 1; mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &attrMaxLength); mysqlStmtPrepare(stmt, this->m_query.c_str()); if (database.shouldCachePreparedStatements()) { - this->cachedStatement = stmt; - database.cacheStatement(stmt); + this->cachedStatement = database.cacheStatement(stmt); } } unsigned int parameterCount = mysql_stmt_param_count(stmt); @@ -235,51 +230,16 @@ void PreparedQuery::executeQuery(Database &database, MYSQL *connection, const st } } catch (const MySQLException &error) { unsigned int errorCode = error.getErrorCode(); - if (errorCode == ER_UNKNOWN_STMT_HANDLER || errorCode == CR_NO_PREPARE_STMT) { - //In this case, the statement is lost on the server (usually after a reconnect). - //Since the statement is unknown, nothing has been executed yet (i.e. no side effects), - //and we are perfectly fine to re-prepare the statement and try again, even if auto-reconnect - //is disabled. - database.freeStatement(this->cachedStatement); - this->cachedStatement = nullptr; - if (data->firstAttempt) { - data->firstAttempt = false; - executeQuery(database, connection, ptr); - return; - } - } else if (errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR || - errorCode == ER_MAX_PREPARED_STMT_COUNT_REACHED) { + if (errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR || + errorCode == ER_MAX_PREPARED_STMT_COUNT_REACHED || errorCode == ER_UNKNOWN_STMT_HANDLER || + errorCode == CR_NO_PREPARE_STMT) { + //In these cases the statement will no longer be valid, free it. database.freeStatement(this->cachedStatement); - this->cachedStatement = nullptr; - //Because autoreconnect is disabled we want to try and explicitly execute the prepared query once more - //if we can get the client to reconnect (reconnect is caused by mysql_ping) - //If this fails we just go ahead and error - if (shouldReconnect && data->firstAttempt) { - if (mysql_ping(connection) == 0) { - data->firstAttempt = false; - executeQuery(database, connection, ptr); - return; - } - } } - //Rethrow error to be handled by executeStatement() throw error; } } -bool PreparedQuery::executeStatement(Database &database, MYSQL *connection, std::shared_ptr ptr) { - std::shared_ptr data = std::dynamic_pointer_cast(ptr); - data->setStatus(QUERY_RUNNING); - try { - this->executeQuery(database, connection, ptr); - data->setResultStatus(QUERY_SUCCESS); - } catch (const MySQLException &error) { - data->setResultStatus(QUERY_ERROR); - data->setError(error.what()); - } - return true; -} - std::shared_ptr PreparedQuery::buildQueryData() { std::shared_ptr data(new PreparedQueryData()); data->m_parameters = this->m_parameters; diff --git a/src/mysql/PreparedQuery.h b/src/mysql/PreparedQuery.h index 209b11a..2be1112 100644 --- a/src/mysql/PreparedQuery.h +++ b/src/mysql/PreparedQuery.h @@ -4,9 +4,9 @@ #include #include "Query.h" #include "MySQLHeader.h" +#include "StatementHandle.h" #include - class PreparedQueryField { friend class PreparedQuery; @@ -42,6 +42,7 @@ class PreparedQueryData : public QueryData { protected: std::deque>> m_parameters; bool firstAttempt = true; + PreparedQueryData() = default; }; @@ -51,7 +52,7 @@ class PreparedQuery : public Query { public: ~PreparedQuery() override; - bool executeStatement(Database &database, MYSQL *connection, std::shared_ptr data) override; + void executeStatement(Database &database, MYSQL *connection, const std::shared_ptr &data) override; void clearParameters(); @@ -68,8 +69,6 @@ class PreparedQuery : public Query { std::shared_ptr buildQueryData() override; static std::shared_ptr create(const std::shared_ptr &dbase, std::string query); -protected: - void executeQuery(Database &database, MYSQL *m_sql, const std::shared_ptr &data) override; private: PreparedQuery(const std::shared_ptr &dbase, std::string query); @@ -92,8 +91,7 @@ class PreparedQuery : public Query { static bool mysqlStmtNextResult(MYSQL_STMT *sql); - //This is atomic to prevent visibility issues - std::atomic cachedStatement{nullptr}; + std::shared_ptr cachedStatement{nullptr}; }; #endif \ No newline at end of file diff --git a/src/mysql/Query.cpp b/src/mysql/Query.cpp index fb69064..5e7dd39 100644 --- a/src/mysql/Query.cpp +++ b/src/mysql/Query.cpp @@ -12,36 +12,23 @@ Query::Query(const std::shared_ptr& dbase, std::string query) : IQuery Query::~Query() = default; -void Query::executeQuery(Database &database, MYSQL* connection, const std::shared_ptr &data) { - auto queryData = std::dynamic_pointer_cast(data); - Query::mysqlQuery(connection, this->m_query); - //Stores all result sets - //MySQL result sets shouldn't be accessed from different threads! - do { - MYSQL_RES * results = Query::mysqlStoreResults(connection); - auto resultFree = finally([&] { mysql_free_result(results); }); - if (results != nullptr) { - queryData->m_results.emplace_back(results); - } else { - queryData->m_results.emplace_back(); - } - queryData->m_insertIds.push_back(mysql_insert_id(connection)); - queryData->m_affectedRows.push_back(mysql_affected_rows(connection)); - } while (Query::mysqlNextResult(connection)); -} - //Executes the raw query -bool Query::executeStatement(Database &database, MYSQL* connection, std::shared_ptr data) { +void Query::executeStatement(Database &database, MYSQL* connection, const std::shared_ptr& data) { auto queryData = std::dynamic_pointer_cast(data); - data->setStatus(QUERY_RUNNING); - try { - this->executeQuery(database, connection, data); - queryData->m_resultStatus = QUERY_SUCCESS; - } catch (const MySQLException& error) { - data->setError(error.what()); - data->setResultStatus(QUERY_ERROR); - } - return true; + Query::mysqlQuery(connection, this->m_query); + //Stores all result sets + //MySQL result sets shouldn't be accessed from different threads! + do { + MYSQL_RES * results = Query::mysqlStoreResults(connection); + auto resultFree = finally([&] { mysql_free_result(results); }); + if (results != nullptr) { + queryData->m_results.emplace_back(results); + } else { + queryData->m_results.emplace_back(); + } + queryData->m_insertIds.push_back(mysql_insert_id(connection)); + queryData->m_affectedRows.push_back(mysql_affected_rows(connection)); + } while (Query::mysqlNextResult(connection)); } diff --git a/src/mysql/Query.h b/src/mysql/Query.h index 77aee33..3618f67 100644 --- a/src/mysql/Query.h +++ b/src/mysql/Query.h @@ -18,9 +18,7 @@ class Query : public IQuery { public: ~Query() override; - bool executeStatement(Database &database, MYSQL *m_sql, std::shared_ptr data) override; - - virtual void executeQuery(Database &database, MYSQL *m_sql, const std::shared_ptr &data); + void executeStatement(Database &database, MYSQL *m_sql, const std::shared_ptr& data) override; my_ulonglong lastInsert(); diff --git a/src/mysql/StatementHandle.h b/src/mysql/StatementHandle.h new file mode 100644 index 0000000..e34590d --- /dev/null +++ b/src/mysql/StatementHandle.h @@ -0,0 +1,20 @@ +#ifndef MYSQLOO_STATEMENTHANDLE_H +#define MYSQLOO_STATEMENTHANDLE_H + +#include "mysql.h" + +class StatementHandle { +public: + StatementHandle(MYSQL_STMT *stmt, bool valid); + + MYSQL_STMT *stmt = nullptr; + + bool isValid() const { return stmt != nullptr && valid; }; + + void invalidate() { valid = false; } + +private: + bool valid; +}; + +#endif //MYSQLOO_STATEMENTHANDLE_H diff --git a/src/mysql/Transaction.cpp b/src/mysql/Transaction.cpp index 880c4d4..d59e285 100644 --- a/src/mysql/Transaction.cpp +++ b/src/mysql/Transaction.cpp @@ -1,73 +1,73 @@ #include "Transaction.h" #include -#include "errmsg.h" #include "Database.h" #include "mysqld_error.h" -bool Transaction::executeStatement(Database &database, MYSQL *connection, std::shared_ptr ptr) { + +void Transaction::executeStatement(Database &database, MYSQL *connection, const std::shared_ptr& ptr) { std::shared_ptr data = std::dynamic_pointer_cast(ptr); data->setStatus(QUERY_RUNNING); - //This temporarily disables reconnect, since a reconnect - //would rollback (and cancel) a transaction - //Which could lead to parts of the transaction being executed outside of a transaction - //If they are being executed after the reconnect - bool oldReconnectStatus = database.getSQLAutoReconnect(); - database.setSQLAutoReconnect(false); - auto resetReconnectStatus = finally([&] { database.setSQLAutoReconnect(oldReconnectStatus); }); try { - Transaction::mysqlAutocommit(connection, false); - { - for (auto &query: data->m_queries) { - try { - //Errors are cleared in case this is retrying after losing connection - query.second->setResultStatus(QUERY_NONE); - query.second->setError(""); - query.first->executeQuery(database, connection, query.second); - } catch (const MySQLException &error) { - query.second->setError(error.what()); - query.second->setResultStatus(QUERY_ERROR); - throw error; - } + for (auto &query: data->m_queries) { + //Errors are cleared in case this is retrying after losing connection + query.second->setStatus(QUERY_RUNNING); + query.second->setResultStatus(QUERY_NONE); + query.second->setError(""); + } + + mysqlAutocommit(connection, false); + + for (auto &query: data->m_queries) { + try { + query.first->executeStatement(database, connection, query.second); + } catch (const MySQLException &error) { + query.second->setError(error.what()); + query.second->setResultStatus(QUERY_ERROR); + throw error; } } - mysql_commit(connection); + + mysqlCommit(connection); data->setResultStatus(QUERY_SUCCESS); - Transaction::mysqlAutocommit(connection, true); + //If this fails the connection was lost but the transaction was already executed fully + //We do not want to throw an error here so the result is ignored. + mysql_autocommit(connection, true); + applyChildResultStatus(data); } catch (const MySQLException &error) { - //This check makes sure that setting mysqlAutocommit back to true doesn't cause the transaction to fail - //Even though the transaction was executed successfully - if (data->getResultStatus() != QUERY_SUCCESS) { - unsigned int errorCode = error.getErrorCode(); - if (oldReconnectStatus && !data->retried && - (errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR)) { - //Because autoreconnect is disabled we want to try and explicitly execute the transaction once more - //if we can get the client to reconnect (reconnect is caused by mysql_ping) - //If this fails we just go ahead and error - database.setSQLAutoReconnect(true); - if (mysql_ping(connection) == 0) { - data->retried = true; - return executeStatement(database, connection, ptr); - } - } - //If this call fails it means that the connection was (probably) lost - //In that case the mysql server rolls back any transaction anyways so it doesn't - //matter if it fails - mysql_rollback(connection); - data->setResultStatus(QUERY_ERROR); - } + data->setResultStatus(QUERY_ERROR); + mysql_rollback(connection); //If this fails it probably means that the connection was lost - //In that case autocommit is turned back on anyways (once the connection is reestablished) - //See: https://dev.mysql.com/doc/refman/5.7/en/auto-reconnect.html + //In that case autocommit is turned back on anyway (once the connection is reestablished) mysql_autocommit(connection, true); - data->setError(error.what()); + //In case of reconnect this might get called twice, but this should not affect anything + applyChildResultStatus(data); + throw error; } +} + +void Transaction::applyChildResultStatus(const std::shared_ptr& data) { for (auto &pair: data->m_queries) { pair.second->setResultStatus(data->getResultStatus()); pair.second->setStatus(QUERY_COMPLETE); } - data->setStatus(QUERY_COMPLETE); - return true; +} + +void Transaction::mysqlAutocommit(MYSQL *sql, bool auto_mode) { + my_bool result = mysql_autocommit(sql, (my_bool) auto_mode); + if (result) { + const char *errorMessage = mysql_error(sql); + unsigned int errorCode = mysql_errno(sql); + throw MySQLException(errorCode, errorMessage); + } +} + +void Transaction::mysqlCommit(MYSQL *sql) { + if (mysql_commit(sql)) { + const char *errorMessage = mysql_error(sql); + unsigned int errorCode = mysql_errno(sql); + throw MySQLException(errorCode, errorMessage); + } } @@ -80,4 +80,4 @@ Transaction::buildQueryData(const std::deque, s std::shared_ptr Transaction::create(const std::shared_ptr &database) { return std::shared_ptr(new Transaction(database)); -} +} \ No newline at end of file diff --git a/src/mysql/Transaction.h b/src/mysql/Transaction.h index ac4234d..7df286e 100644 --- a/src/mysql/Transaction.h +++ b/src/mysql/Transaction.h @@ -19,8 +19,6 @@ class TransactionData : public IQueryData { explicit TransactionData(std::deque, std::shared_ptr>> queries) : m_queries(std::move(queries)) { }; - - bool retried = false; }; class Transaction : public IQuery { @@ -33,11 +31,17 @@ class Transaction : public IQuery { static std::shared_ptr create(const std::shared_ptr &database); protected: - bool executeStatement(Database &database, MYSQL *connection, std::shared_ptr data) override; + void executeStatement(Database &database, MYSQL *connection, const std::shared_ptr& data) override; explicit Transaction(const std::shared_ptr &database) : IQuery(database) { } +private: + static void applyChildResultStatus(const std::shared_ptr& data); + + static void mysqlAutocommit(MYSQL *sql, bool auto_mode); + + static void mysqlCommit(MYSQL *sql); }; #endif