diff options
author | Kargatum <dowlandtop@yandex.com> | 2021-06-22 11:21:07 +0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-22 06:21:07 +0200 |
commit | 9ac6fddcae9837f468f52369f0f963da7c41569a (patch) | |
tree | e8ff4b0ee6f14167cb532c0927335bfdc47b98f0 /src/server/database/Database/MySQLConnection.cpp | |
parent | 2a2e54d8c586370bf82fee3d9e5b360af358bd6e (diff) |
feat(Core/Database): port TrinityCore database API (#5611)
Diffstat (limited to 'src/server/database/Database/MySQLConnection.cpp')
-rw-r--r-- | src/server/database/Database/MySQLConnection.cpp | 493 |
1 files changed, 283 insertions, 210 deletions
diff --git a/src/server/database/Database/MySQLConnection.cpp b/src/server/database/Database/MySQLConnection.cpp index 2abf885136..f2f10d4d91 100644 --- a/src/server/database/Database/MySQLConnection.cpp +++ b/src/server/database/Database/MySQLConnection.cpp @@ -1,54 +1,71 @@ /* - * Copyright (C) 2016+ AzerothCore <www.azerothcore.org> - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> */ -#include "Common.h" #include "MySQLConnection.h" -#include "MySQLThreading.h" -#include "QueryResult.h" -#include "SQLOperation.h" -#include "PreparedStatement.h" +#include "Common.h" #include "DatabaseWorker.h" -#include "Timer.h" #include "Log.h" -#include "Duration.h" -#include <mysql.h> -#include <mysqld_error.h> +#include "MySQLHacks.h" +#include "MySQLPreparedStatement.h" +#include "MySQLWorkaround.h" +#include "PreparedStatement.h" +#include "QueryResult.h" +#include "Timer.h" +#include "Tokenize.h" +#include "Transaction.h" +#include "Util.h" #include <errmsg.h> -#include <thread> +#include <mysqld_error.h> -#ifdef _WIN32 -#include <winsock2.h> -#endif +MySQLConnectionInfo::MySQLConnectionInfo(std::string const& infoString) +{ + std::vector<std::string_view> tokens = Acore::Tokenize(infoString, ';', true); + + if (tokens.size() != 5 && tokens.size() != 6) + return; + + host.assign(tokens[0]); + port_or_socket.assign(tokens[1]); + user.assign(tokens[2]); + password.assign(tokens[3]); + database.assign(tokens[4]); + + if (tokens.size() == 6) + ssl.assign(tokens[5]); +} MySQLConnection::MySQLConnection(MySQLConnectionInfo& connInfo) : - m_reconnecting(false), - m_prepareError(false), - m_queue(nullptr), - m_worker(nullptr), - m_Mysql(nullptr), - m_connectionInfo(connInfo), - m_connectionFlags(CONNECTION_SYNCH) +m_reconnecting(false), +m_prepareError(false), +m_queue(nullptr), +m_Mysql(nullptr), +m_connectionInfo(connInfo), +m_connectionFlags(CONNECTION_SYNCH) { } + +MySQLConnection::MySQLConnection(ProducerConsumerQueue<SQLOperation*>* queue, MySQLConnectionInfo& connInfo) : +m_reconnecting(false), +m_prepareError(false), +m_queue(queue), +m_Mysql(nullptr), +m_connectionInfo(connInfo), +m_connectionFlags(CONNECTION_ASYNC) { + m_worker = std::make_unique<DatabaseWorker>(m_queue, this); } -MySQLConnection::MySQLConnection(ACE_Activation_Queue* queue, MySQLConnectionInfo& connInfo) : - m_reconnecting(false), - m_prepareError(false), - m_queue(queue), - m_Mysql(nullptr), - m_connectionInfo(connInfo), - m_connectionFlags(CONNECTION_ASYNC) +MySQLConnection::~MySQLConnection() { - m_worker = new DatabaseWorker(m_queue, this); + Close(); } -MySQLConnection::~MySQLConnection() +void MySQLConnection::Close() { - for (auto stmt : m_stmts) - delete stmt; + // Stop the worker thread before the statements are cleared + m_worker.reset(); + + m_stmts.clear(); if (m_Mysql) { @@ -57,19 +74,14 @@ MySQLConnection::~MySQLConnection() } } -void MySQLConnection::Close() -{ - /// Only close us if we're not operating - delete this; -} - uint32 MySQLConnection::Open() { - MYSQL* mysqlInit = mysql_init(nullptr); + MYSQL *mysqlInit; + mysqlInit = mysql_init(nullptr); if (!mysqlInit) { LOG_ERROR("sql.sql", "Could not initialize Mysql connection to database `%s`", m_connectionInfo.database.c_str()); - return false; + return CR_UNKNOWN_ERROR; } int port; @@ -78,7 +90,7 @@ uint32 MySQLConnection::Open() mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8"); //mysql_options(mysqlInit, MYSQL_OPT_READ_TIMEOUT, (char const*)&timeout); -#ifdef _WIN32 + #ifdef _WIN32 if (m_connectionInfo.host == ".") // named pipe use option (Windows) { unsigned int opt = MYSQL_PROTOCOL_PIPE; @@ -91,7 +103,7 @@ uint32 MySQLConnection::Open() port = atoi(m_connectionInfo.port_or_socket.c_str()); unix_socket = 0; } -#else + #else if (m_connectionInfo.host == ".") // socket use option (Unix/Linux) { unsigned int opt = MYSQL_PROTOCOL_SOCKET; @@ -103,19 +115,31 @@ uint32 MySQLConnection::Open() else // generic case { port = atoi(m_connectionInfo.port_or_socket.c_str()); - unix_socket = 0; + unix_socket = nullptr; } + #endif + + if (m_connectionInfo.ssl != "") + { +#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80000 + mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED; + if (m_connectionInfo.ssl == "ssl") + { + opt_use_ssl = SSL_MODE_REQUIRED; + } + mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl); +#else + MySQLBool opt_use_ssl = MySQLBool(0); + if (m_connectionInfo.ssl == "ssl") + { + opt_use_ssl = MySQLBool(1); + } + mysql_options(mysqlInit, MYSQL_OPT_SSL_ENFORCE, (char const*)&opt_use_ssl); #endif + } - m_Mysql = mysql_real_connect( - mysqlInit, - m_connectionInfo.host.c_str(), - m_connectionInfo.user.c_str(), - m_connectionInfo.password.c_str(), - m_connectionInfo.database.c_str(), - port, - unix_socket, - 0); + m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(), + m_connectionInfo.password.c_str(), m_connectionInfo.database.c_str(), port, unix_socket, 0)); if (m_Mysql) { @@ -123,15 +147,12 @@ uint32 MySQLConnection::Open() { LOG_INFO("sql.sql", "MySQL client library: %s", mysql_get_client_info()); LOG_INFO("sql.sql", "MySQL server ver: %s ", mysql_get_server_info(m_Mysql)); - - if (mysql_get_server_version(m_Mysql) != mysql_get_client_version()) - { - LOG_WARN("sql.sql", "[WARNING] MySQL client/server version mismatch; may conflict with behaviour of prepared statements."); - } + // MySQL version above 5.1 IS required in both client and server and there is no known issue with different versions above 5.1 + // if (mysql_get_server_version(m_Mysql) != mysql_get_client_version()) + // LOG_INFO("sql.sql", "[WARNING] MySQL client/server version mismatch; may conflict with behaviour of prepared statements."); } LOG_INFO("sql.sql", "Connected to MySQL database at %s", m_connectionInfo.host.c_str()); - mysql_autocommit(m_Mysql, 1); // set connection properties to UTF8 to properly handle locales for different @@ -139,11 +160,13 @@ uint32 MySQLConnection::Open() mysql_set_character_set(m_Mysql, "utf8"); return 0; } - - LOG_ERROR("sql.sql", "Could not connect to MySQL database at %s: %s", m_connectionInfo.host.c_str(), mysql_error(mysqlInit)); - uint32 errorCode = mysql_errno(mysqlInit); - mysql_close(mysqlInit); - return errorCode; + else + { + LOG_ERROR("sql.sql", "Could not connect to MySQL database at %s: %s", m_connectionInfo.host.c_str(), mysql_error(mysqlInit)); + uint32 errorCode = mysql_errno(mysqlInit); + mysql_close(mysqlInit); + return errorCode; + } } bool MySQLConnection::PrepareStatements() @@ -152,144 +175,141 @@ bool MySQLConnection::PrepareStatements() return !m_prepareError; } -bool MySQLConnection::Execute(const char* sql) +bool MySQLConnection::Execute(char const* sql) { if (!m_Mysql) return false; - uint32 _s = getMSTime(); - - if (mysql_query(m_Mysql, sql)) { - uint32 lErrno = mysql_errno(m_Mysql); + uint32 _s = getMSTime(); - LOG_ERROR("sql.sql", "SQL: %s", sql); - LOG_ERROR("sql.sql", "ERROR: [%u] %s", lErrno, mysql_error(m_Mysql)); + if (mysql_query(m_Mysql, sql)) + { + uint32 lErrno = mysql_errno(m_Mysql); - if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) - return Execute(sql); // Try again + LOG_INFO("sql.sql", "SQL: %s", sql); + LOG_ERROR("sql.sql", "[%u] %s", lErrno, mysql_error(m_Mysql)); - return false; - } + if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) + return Execute(sql); // Try again - LOG_DEBUG("sql.sql", "[%u ms] SQL: %s", getMSTimeDiff(_s, getMSTime()), sql); + return false; + } + else + LOG_DEBUG("sql.sql", "[%u ms] SQL: %s", getMSTimeDiff(_s, getMSTime()), sql); + } return true; } -bool MySQLConnection::Execute(PreparedStatement* stmt) +bool MySQLConnection::Execute(PreparedStatementBase* stmt) { if (!m_Mysql) return false; - uint32 index = stmt->m_index; - { - MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index); - ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query - m_mStmt->m_stmt = stmt; // Cross reference them for debug output - stmt->m_stmt = m_mStmt; // TODO: Cleaner way + uint32 index = stmt->GetIndex(); - stmt->BindParameters(); + MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index); + ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query - MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT(); - MYSQL_BIND* msql_BIND = m_mStmt->GetBind(); + m_mStmt->BindParameters(stmt); - uint32 _s = getMSTime(); - - if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) - { - uint32 lErrno = mysql_errno(m_Mysql); - LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString(m_queries[index].first).c_str(), lErrno, mysql_stmt_error(msql_STMT)); + MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT(); + MYSQL_BIND* msql_BIND = m_mStmt->GetBind(); - if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) - return Execute(stmt); // Try again + uint32 _s = getMSTime(); - m_mStmt->ClearParameters(); - return false; - } + if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) + { + uint32 lErrno = mysql_errno(m_Mysql); + LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString().c_str(), lErrno, mysql_stmt_error(msql_STMT)); - if (mysql_stmt_execute(msql_STMT)) - { - uint32 lErrno = mysql_errno(m_Mysql); - LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString(m_queries[index].first).c_str(), lErrno, mysql_stmt_error(msql_STMT)); + if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) + return Execute(stmt); // Try again - if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) - return Execute(stmt); // Try again + m_mStmt->ClearParameters(); + return false; + } - m_mStmt->ClearParameters(); - return false; - } + if (mysql_stmt_execute(msql_STMT)) + { + uint32 lErrno = mysql_errno(m_Mysql); + LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString().c_str(), lErrno, mysql_stmt_error(msql_STMT)); - LOG_DEBUG("sql.sql", "[%u ms] SQL(p): %s", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString(m_queries[index].first).c_str()); + if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) + return Execute(stmt); // Try again m_mStmt->ClearParameters(); - return true; + return false; } + + LOG_DEBUG("sql.sql", "[%u ms] SQL(p): %s", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString().c_str()); + + m_mStmt->ClearParameters(); + return true; } -bool MySQLConnection::_Query(PreparedStatement* stmt, MYSQL_RES** pResult, uint64* pRowCount, uint32* pFieldCount) +bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement** mysqlStmt, MySQLResult** pResult, uint64* pRowCount, uint32* pFieldCount) { if (!m_Mysql) return false; - uint32 index = stmt->m_index; - { - MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index); - ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query - m_mStmt->m_stmt = stmt; // Cross reference them for debug output - stmt->m_stmt = m_mStmt; // TODO: Cleaner way - - stmt->BindParameters(); + uint32 index = stmt->GetIndex(); - MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT(); - MYSQL_BIND* msql_BIND = m_mStmt->GetBind(); + MySQLPreparedStatement* m_mStmt = GetPreparedStatement(index); + ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query - uint32 _s = getMSTime(); + m_mStmt->BindParameters(stmt); + *mysqlStmt = m_mStmt; - if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) - { - uint32 lErrno = mysql_errno(m_Mysql); - LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString(m_queries[index].first).c_str(), lErrno, mysql_stmt_error(msql_STMT)); + MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT(); + MYSQL_BIND* msql_BIND = m_mStmt->GetBind(); - if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) - return _Query(stmt, pResult, pRowCount, pFieldCount); // Try again + uint32 _s = getMSTime(); - m_mStmt->ClearParameters(); - return false; - } + if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) + { + uint32 lErrno = mysql_errno(m_Mysql); + LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", m_mStmt->getQueryString().c_str(), lErrno, mysql_stmt_error(msql_STMT)); - if (mysql_stmt_execute(msql_STMT)) - { - uint32 lErrno = mysql_errno(m_Mysql); - LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", - m_mStmt->getQueryString(m_queries[index].first).c_str(), lErrno, mysql_stmt_error(msql_STMT)); + if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) + return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again - if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) - return _Query(stmt, pResult, pRowCount, pFieldCount); // Try again + m_mStmt->ClearParameters(); + return false; + } - m_mStmt->ClearParameters(); - return false; - } + if (mysql_stmt_execute(msql_STMT)) + { + uint32 lErrno = mysql_errno(m_Mysql); + LOG_ERROR("sql.sql", "SQL(p): %s\n [ERROR]: [%u] %s", + m_mStmt->getQueryString().c_str(), lErrno, mysql_stmt_error(msql_STMT)); - LOG_DEBUG("sql.sql", "[%u ms] SQL(p): %s", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString(m_queries[index].first).c_str()); + if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) + return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again m_mStmt->ClearParameters(); + return false; + } - *pResult = mysql_stmt_result_metadata(msql_STMT); - *pRowCount = mysql_stmt_num_rows(msql_STMT); - *pFieldCount = mysql_stmt_field_count(msql_STMT); + LOG_DEBUG("sql.sql", "[%u ms] SQL(p): %s", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString().c_str()); - return true; - } + m_mStmt->ClearParameters(); + + *pResult = reinterpret_cast<MySQLResult*>(mysql_stmt_result_metadata(msql_STMT)); + *pRowCount = mysql_stmt_num_rows(msql_STMT); + *pFieldCount = mysql_stmt_field_count(msql_STMT); + + return true; } -ResultSet* MySQLConnection::Query(const char* sql) +ResultSet* MySQLConnection::Query(char const* sql) { if (!sql) return nullptr; - MYSQL_RES* result = nullptr; - MYSQL_FIELD* fields = nullptr; + MySQLResult* result = nullptr; + MySQLField* fields = nullptr; uint64 rowCount = 0; uint32 fieldCount = 0; @@ -299,7 +319,7 @@ ResultSet* MySQLConnection::Query(const char* sql) return new ResultSet(result, fields, rowCount, fieldCount); } -bool MySQLConnection::_Query(const char* sql, MYSQL_RES** pResult, MYSQL_FIELD** pFields, uint64* pRowCount, uint32* pFieldCount) +bool MySQLConnection::_Query(const char* sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount) { if (!m_Mysql) return false; @@ -310,23 +330,23 @@ bool MySQLConnection::_Query(const char* sql, MYSQL_RES** pResult, MYSQL_FIELD** if (mysql_query(m_Mysql, sql)) { uint32 lErrno = mysql_errno(m_Mysql); - LOG_ERROR("sql.sql", "SQL: %s", sql); - LOG_ERROR("sql.sql", "ERROR: [%u] %s", lErrno, mysql_error(m_Mysql)); + LOG_INFO("sql.sql", "SQL: %s", sql); + LOG_ERROR("sql.sql", "[%u] %s", lErrno, mysql_error(m_Mysql)); if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection) return _Query(sql, pResult, pFields, pRowCount, pFieldCount); // We try again return false; } + else + LOG_DEBUG("sql.sql", "[%u ms] SQL: %s", getMSTimeDiff(_s, getMSTime()), sql); - LOG_DEBUG("sql.sql", "[%u ms] SQL: %s", getMSTimeDiff(_s, getMSTime()), sql); - - *pResult = mysql_store_result(m_Mysql); + *pResult = reinterpret_cast<MySQLResult*>(mysql_store_result(m_Mysql)); *pRowCount = mysql_affected_rows(m_Mysql); *pFieldCount = mysql_field_count(m_Mysql); } - if (!*pResult) + if (!*pResult ) return false; if (!*pRowCount) @@ -335,7 +355,7 @@ bool MySQLConnection::_Query(const char* sql, MYSQL_RES** pResult, MYSQL_FIELD** return false; } - *pFields = mysql_fetch_fields(*pResult); + *pFields = reinterpret_cast<MySQLField*>(mysql_fetch_fields(*pResult)); return true; } @@ -355,27 +375,26 @@ void MySQLConnection::CommitTransaction() Execute("COMMIT"); } -int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) +int MySQLConnection::ExecuteTransaction(std::shared_ptr<TransactionBase> transaction) { - std::list<SQLElementData> const& queries = transaction->m_queries; + std::vector<SQLElementData> const& queries = transaction->m_queries; if (queries.empty()) return -1; BeginTransaction(); - std::list<SQLElementData>::const_iterator itr; - for (itr = queries.begin(); itr != queries.end(); ++itr) + for (auto itr = queries.begin(); itr != queries.end(); ++itr) { SQLElementData const& data = *itr; switch (itr->type) { case SQL_ELEMENT_PREPARED: { - PreparedStatement* stmt = data.element.stmt; + PreparedStatementBase* stmt = data.element.stmt; ASSERT(stmt); if (!Execute(stmt)) { - LOG_INFO("sql.driver", "[Warning] Transaction aborted. %u queries not executed.", (uint32)queries.size()); + LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size()); int errorCode = GetLastError(); RollbackTransaction(); return errorCode; @@ -384,11 +403,11 @@ int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) break; case SQL_ELEMENT_RAW: { - const char* sql = data.element.query; + char const* sql = data.element.query; ASSERT(sql); if (!Execute(sql)) { - LOG_INFO("sql.driver", "[Warning] Transaction aborted. %u queries not executed.", (uint32)queries.size()); + LOG_WARN("sql.sql", "Transaction aborted. %u queries not executed.", (uint32)queries.size()); int errorCode = GetLastError(); RollbackTransaction(); return errorCode; @@ -407,103 +426,157 @@ int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction) return 0; } +size_t MySQLConnection::EscapeString(char* to, const char* from, size_t length) +{ + return mysql_real_escape_string(m_Mysql, to, from, length); +} + +void MySQLConnection::Ping() +{ + mysql_ping(m_Mysql); +} + +uint32 MySQLConnection::GetLastError() +{ + return mysql_errno(m_Mysql); +} + +bool MySQLConnection::LockIfReady() +{ + return m_Mutex.try_lock(); +} + +void MySQLConnection::Unlock() +{ + m_Mutex.unlock(); +} + +uint32 MySQLConnection::GetServerVersion() const +{ + return mysql_get_server_version(m_Mysql); +} + MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index) { - ASSERT(index < m_stmts.size()); - MySQLPreparedStatement* ret = m_stmts[index]; + ASSERT(index < m_stmts.size(), "Tried to access invalid prepared statement index %u (max index " SZFMTD ") on database `%s`, connection type: %s", + index, m_stmts.size(), m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); + MySQLPreparedStatement* ret = m_stmts[index].get(); if (!ret) - LOG_INFO("sql.driver", "ERROR: Could not fetch prepared statement %u on database `%s`, connection type: %s.", - index, m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); + LOG_ERROR("sql.sql", "Could not fetch prepared statement %u on database `%s`, connection type: %s.", + index, m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); return ret; } -void MySQLConnection::PrepareStatement(uint32 index, const char* sql, ConnectionFlags flags) +void MySQLConnection::PrepareStatement(uint32 index, std::string const& sql, ConnectionFlags flags) { - m_queries.insert(PreparedStatementMap::value_type(index, std::make_pair(sql, flags))); - - // For reconnection case - if (m_reconnecting) - delete m_stmts[index]; - // Check if specified query should be prepared on this connection // i.e. don't prepare async statements on synchronous connections // to save memory that will not be used. if (!(m_connectionFlags & flags)) { - m_stmts[index] = nullptr; + m_stmts[index].reset(); return; } MYSQL_STMT* stmt = mysql_stmt_init(m_Mysql); if (!stmt) { - LOG_INFO("sql.driver", "[ERROR]: In mysql_stmt_init() id: %u, sql: \"%s\"", index, sql); - LOG_INFO("sql.driver", "[ERROR]: %s", mysql_error(m_Mysql)); + LOG_ERROR("sql.sql", "In mysql_stmt_init() id: %u, sql: \"%s\"", index, sql.c_str()); + LOG_ERROR("sql.sql", "%s", mysql_error(m_Mysql)); m_prepareError = true; } else { - if (mysql_stmt_prepare(stmt, sql, static_cast<unsigned long>(strlen(sql)))) + if (mysql_stmt_prepare(stmt, sql.c_str(), static_cast<unsigned long>(sql.size()))) { - LOG_INFO("sql.driver", "[ERROR]: In mysql_stmt_prepare() id: %u, sql: \"%s\"", index, sql); - LOG_INFO("sql.driver", "[ERROR]: %s", mysql_stmt_error(stmt)); + LOG_ERROR("sql.sql", "In mysql_stmt_prepare() id: %u, sql: \"%s\"", index, sql.c_str()); + LOG_ERROR("sql.sql", "%s", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); m_prepareError = true; } else - { - MySQLPreparedStatement* mStmt = new MySQLPreparedStatement(stmt); - m_stmts[index] = mStmt; - } + m_stmts[index] = std::make_unique<MySQLPreparedStatement>(reinterpret_cast<MySQLStmt*>(stmt), sql); } } -PreparedResultSet* MySQLConnection::Query(PreparedStatement* stmt) +PreparedResultSet* MySQLConnection::Query(PreparedStatementBase* stmt) { - MYSQL_RES* result = nullptr; + MySQLPreparedStatement* mysqlStmt = nullptr; + MySQLResult* result = nullptr; uint64 rowCount = 0; uint32 fieldCount = 0; - if (!_Query(stmt, &result, &rowCount, &fieldCount)) + if (!_Query(stmt, &mysqlStmt, &result, &rowCount, &fieldCount)) return nullptr; if (mysql_more_results(m_Mysql)) { mysql_next_result(m_Mysql); } - return new PreparedResultSet(stmt->m_stmt->GetSTMT(), result, rowCount, fieldCount); + return new PreparedResultSet(mysqlStmt->GetSTMT(), result, rowCount, fieldCount); } -bool MySQLConnection::_HandleMySQLErrno(uint32 errNo) +bool MySQLConnection::_HandleMySQLErrno(uint32 errNo, uint8 attempts /*= 5*/) { switch (errNo) { case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: case CR_SERVER_LOST_EXTENDED: -#if !(MARIADB_VERSION_ID >= 100200) - case CR_INVALID_CONN_HANDLE: -#endif { + if (m_Mysql) + { + LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!"); + + mysql_close(m_Mysql); + m_Mysql = nullptr; + } + [[fallthrough]]; + } + case CR_CONN_HOST_ERROR: + { + LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server..."); + m_reconnecting = true; - uint64 oldThreadId = mysql_thread_id(GetHandle()); - mysql_close(GetHandle()); - if (this->Open()) // Don't remove 'this' pointer unless you want to skip loading all prepared statements.... + + uint32 const lErrno = Open(); + if (!lErrno) { - LOG_INFO("sql.driver", "Connection to the MySQL server is active."); - if (oldThreadId != mysql_thread_id(GetHandle())) - LOG_INFO("sql.driver", "Successfully reconnected to %s @%s:%s (%s).", - m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(), - (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); + // Don't remove 'this' pointer unless you want to skip loading all prepared statements... + if (!this->PrepareStatements()) + { + LOG_FATAL("sql.sql", "Could not re-prepare statements!"); + std::this_thread::sleep_for(std::chrono::seconds(10)); + std::abort(); + } + + LOG_INFO("sql.sql", "Successfully reconnected to %s @%s:%s (%s).", + m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(), + (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); m_reconnecting = false; return true; } - uint32 lErrno = mysql_errno(GetHandle()); // It's possible this attempted reconnect throws 2006 at us. To prevent crazy recursive calls, sleep here. - std::this_thread::sleep_for(3s); // Sleep 3 seconds - return _HandleMySQLErrno(lErrno); // Call self (recursive) + if ((--attempts) == 0) + { + // Shut down the server when the mysql server isn't + // reachable for some time + LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, " + "terminating the server to prevent data corruption!"); + + // We could also initiate a shutdown through using std::raise(SIGTERM) + std::this_thread::sleep_for(std::chrono::seconds(10)); + std::abort(); + } + else + { + // It's possible this attempted reconnect throws 2006 at us. + // To prevent crazy recursive calls, sleep here. + std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds + return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive) + } } case ER_LOCK_DEADLOCK: @@ -516,17 +589,17 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo) // Outdated table or database structure - terminate core case ER_BAD_FIELD_ERROR: case ER_NO_SUCH_TABLE: - LOG_ERROR("server", "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders."); - std::this_thread::sleep_for(10s); + LOG_ERROR("sql.sql", "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders."); + std::this_thread::sleep_for(std::chrono::seconds(10)); std::abort(); return false; case ER_PARSE_ERROR: - LOG_ERROR("server", "Error while parsing SQL. Core fix required."); - std::this_thread::sleep_for(10s); + LOG_ERROR("sql.sql", "Error while parsing SQL. Core fix required."); + std::this_thread::sleep_for(std::chrono::seconds(10)); std::abort(); return false; default: - LOG_ERROR("server", "Unhandled MySQL errno %u. Unexpected behaviour possible.", errNo); + LOG_ERROR("sql.sql", "Unhandled MySQL errno %u. Unexpected behaviour possible.", errNo); return false; } } |