TrinityCore
Loading...
Searching...
No Matches
MySQLConnection.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "MySQLConnection.h"
19#include "Common.h"
20#include "DatabaseWorker.h"
21#include "Log.h"
22#include "MySQLHacks.h"
24#include "PreparedStatement.h"
25#include "QueryResult.h"
26#include "Timer.h"
27#include "Transaction.h"
28#include "Util.h"
29#include <errmsg.h>
30#include "MySQLWorkaround.h"
31#include <mysqld_error.h>
32
33MySQLConnectionInfo::MySQLConnectionInfo(std::string const& infoString)
34{
35 std::vector<std::string_view> tokens = Trinity::Tokenize(infoString, ';', true);
36
37 if (tokens.size() != 5 && tokens.size() != 6)
38 return;
39
40 host.assign(tokens[0]);
41 port_or_socket.assign(tokens[1]);
42 user.assign(tokens[2]);
43 password.assign(tokens[3]);
44 database.assign(tokens[4]);
45
46 if (tokens.size() == 6)
47 ssl.assign(tokens[5]);
48}
49
51m_reconnecting(false),
52m_prepareError(false),
53m_queue(nullptr),
54m_Mysql(nullptr),
55m_connectionInfo(connInfo),
56m_connectionFlags(CONNECTION_SYNCH) { }
57
59m_reconnecting(false),
60m_prepareError(false),
61m_queue(queue),
62m_Mysql(nullptr),
63m_connectionInfo(connInfo),
64m_connectionFlags(CONNECTION_ASYNC)
65{
66 m_worker = std::make_unique<DatabaseWorker>(m_queue, this);
67}
68
73
75{
76 // Stop the worker thread before the statements are cleared
77 m_worker.reset();
78
79 m_stmts.clear();
80
81 if (m_Mysql)
82 {
83 mysql_close(m_Mysql);
84 m_Mysql = nullptr;
85 }
86}
87
89{
90 MYSQL *mysqlInit;
91 mysqlInit = mysql_init(nullptr);
92 if (!mysqlInit)
93 {
94 TC_LOG_ERROR("sql.sql", "Could not initialize Mysql connection to database `{}`", m_connectionInfo.database);
95 return CR_UNKNOWN_ERROR;
96 }
97
98 int port;
99 char const* unix_socket;
100 //unsigned int timeout = 10;
101
102 mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8mb4");
103 //mysql_options(mysqlInit, MYSQL_OPT_READ_TIMEOUT, (char const*)&timeout);
104 #ifdef _WIN32
105 if (m_connectionInfo.host == ".") // named pipe use option (Windows)
106 {
107 unsigned int opt = MYSQL_PROTOCOL_PIPE;
108 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
109 port = 0;
110 unix_socket = 0;
111 }
112 else // generic case
113 {
114 port = atoi(m_connectionInfo.port_or_socket.c_str());
115 unix_socket = 0;
116 }
117 #else
118 if (m_connectionInfo.host == ".") // socket use option (Unix/Linux)
119 {
120 unsigned int opt = MYSQL_PROTOCOL_SOCKET;
121 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
122 m_connectionInfo.host = "localhost";
123 port = 0;
124 unix_socket = m_connectionInfo.port_or_socket.c_str();
125 }
126 else // generic case
127 {
128 port = atoi(m_connectionInfo.port_or_socket.c_str());
129 unix_socket = nullptr;
130 }
131 #endif
132
133 if (m_connectionInfo.ssl != "")
134 {
135#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80000
136 mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED;
137 if (m_connectionInfo.ssl == "ssl")
138 {
139 opt_use_ssl = SSL_MODE_REQUIRED;
140 }
141 mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl);
142#else
143 MySQLBool opt_use_ssl = MySQLBool(0);
144 if (m_connectionInfo.ssl == "ssl")
145 {
146 opt_use_ssl = MySQLBool(1);
147 }
148 mysql_options(mysqlInit, MYSQL_OPT_SSL_ENFORCE, (char const*)&opt_use_ssl);
149#endif
150 }
151
152 m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(),
153 m_connectionInfo.password.c_str(), m_connectionInfo.database.c_str(), port, unix_socket, 0));
154
155 if (m_Mysql)
156 {
157 if (!m_reconnecting)
158 {
159 TC_LOG_INFO("sql.sql", "MySQL client library: {}", mysql_get_client_info());
160 TC_LOG_INFO("sql.sql", "MySQL server ver: {} ", mysql_get_server_info(m_Mysql));
161 // MySQL version above 5.1 IS required in both client and server and there is no known issue with different versions above 5.1
162 // if (mysql_get_server_version(m_Mysql) != mysql_get_client_version())
163 // TC_LOG_INFO("sql.sql", "[WARNING] MySQL client/server version mismatch; may conflict with behaviour of prepared statements.");
164 }
165
166 TC_LOG_INFO("sql.sql", "Connected to MySQL database at {}", m_connectionInfo.host);
167 mysql_autocommit(m_Mysql, 1);
168
169 // set connection properties to UTF8 to properly handle locales for different
170 // server configs - core sends data in UTF8, so MySQL must expect UTF8 too
171 mysql_set_character_set(m_Mysql, "utf8mb4");
172 return 0;
173 }
174 else
175 {
176 TC_LOG_ERROR("sql.sql", "Could not connect to MySQL database at {}: {}", m_connectionInfo.host, mysql_error(mysqlInit));
177 uint32 errorCode = mysql_errno(mysqlInit);
178 mysql_close(mysqlInit);
179 return errorCode;
180 }
181}
182
188
189bool MySQLConnection::Execute(char const* sql)
190{
191 if (!m_Mysql)
192 return false;
193
194 {
195 uint32 _s = getMSTime();
196
197 if (mysql_query(m_Mysql, sql))
198 {
199 uint32 lErrno = mysql_errno(m_Mysql);
200
201 TC_LOG_INFO("sql.sql", "SQL: {}", sql);
202 TC_LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
203
204 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
205 return Execute(sql); // Try again
206
207 return false;
208 }
209 else
210 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
211 }
212
213 return true;
214}
215
217{
218 if (!m_Mysql)
219 return false;
220
221 uint32 index = stmt->GetIndex();
222
224 ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
225
226 m_mStmt->BindParameters(stmt);
227
228 MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
229 MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
230
231 uint32 _s = getMSTime();
232
233 if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
234 {
235 uint32 lErrno = mysql_errno(m_Mysql);
236 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
237
238 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
239 return Execute(stmt); // Try again
240
241 m_mStmt->ClearParameters();
242 return false;
243 }
244
245 if (mysql_stmt_execute(msql_STMT))
246 {
247 uint32 lErrno = mysql_errno(m_Mysql);
248 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
249
250 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
251 return Execute(stmt); // Try again
252
253 m_mStmt->ClearParameters();
254 return false;
255 }
256
257 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
258
259 m_mStmt->ClearParameters();
260 return true;
261}
262
263bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement** mysqlStmt, MySQLResult** pResult, uint64* pRowCount, uint32* pFieldCount)
264{
265 if (!m_Mysql)
266 return false;
267
268 uint32 index = stmt->GetIndex();
269
271 ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
272
273 m_mStmt->BindParameters(stmt);
274 *mysqlStmt = m_mStmt;
275
276 MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
277 MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
278
279 uint32 _s = getMSTime();
280
281 if (mysql_stmt_bind_param(msql_STMT, msql_BIND))
282 {
283 uint32 lErrno = mysql_errno(m_Mysql);
284 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
285
286 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
287 return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again
288
289 m_mStmt->ClearParameters();
290 return false;
291 }
292
293 if (mysql_stmt_execute(msql_STMT))
294 {
295 uint32 lErrno = mysql_errno(m_Mysql);
296 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}",
297 m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
298
299 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
300 return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again
301
302 m_mStmt->ClearParameters();
303 return false;
304 }
305
306 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
307
308 m_mStmt->ClearParameters();
309
310 *pResult = reinterpret_cast<MySQLResult*>(mysql_stmt_result_metadata(msql_STMT));
311 *pRowCount = mysql_stmt_num_rows(msql_STMT);
312 *pFieldCount = mysql_stmt_field_count(msql_STMT);
313
314 return true;
315}
316
318{
319 if (!sql)
320 return nullptr;
321
322 MySQLResult* result = nullptr;
323 MySQLField* fields = nullptr;
324 uint64 rowCount = 0;
325 uint32 fieldCount = 0;
326
327 if (!_Query(sql, &result, &fields, &rowCount, &fieldCount))
328 return nullptr;
329
330 return new ResultSet(result, fields, rowCount, fieldCount);
331}
332
333bool MySQLConnection::_Query(const char* sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount)
334{
335 if (!m_Mysql)
336 return false;
337
338 {
339 uint32 _s = getMSTime();
340
341 if (mysql_query(m_Mysql, sql))
342 {
343 uint32 lErrno = mysql_errno(m_Mysql);
344 TC_LOG_INFO("sql.sql", "SQL: {}", sql);
345 TC_LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
346
347 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
348 return _Query(sql, pResult, pFields, pRowCount, pFieldCount); // We try again
349
350 return false;
351 }
352 else
353 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
354
355 *pResult = reinterpret_cast<MySQLResult*>(mysql_store_result(m_Mysql));
356 *pRowCount = mysql_affected_rows(m_Mysql);
357 *pFieldCount = mysql_field_count(m_Mysql);
358 }
359
360 if (!*pResult )
361 return false;
362
363 if (!*pRowCount)
364 {
365 mysql_free_result(*pResult);
366 return false;
367 }
368
369 *pFields = reinterpret_cast<MySQLField*>(mysql_fetch_fields(*pResult));
370
371 return true;
372}
373
375{
376 Execute("START TRANSACTION");
377}
378
380{
381 Execute("ROLLBACK");
382}
383
385{
386 Execute("COMMIT");
387}
388
389int MySQLConnection::ExecuteTransaction(std::shared_ptr<TransactionBase> transaction)
390{
391 std::vector<SQLElementData> const& queries = transaction->m_queries;
392 if (queries.empty())
393 return -1;
394
396
397 for (auto itr = queries.begin(); itr != queries.end(); ++itr)
398 {
399 SQLElementData const& data = *itr;
400 switch (itr->type)
401 {
403 {
405 ASSERT(stmt);
406 if (!Execute(stmt))
407 {
408 TC_LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", (uint32)queries.size());
409 int errorCode = GetLastError();
411 return errorCode;
412 }
413 }
414 break;
415 case SQL_ELEMENT_RAW:
416 {
417 char const* sql = data.element.query;
418 ASSERT(sql);
419 if (!Execute(sql))
420 {
421 TC_LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", (uint32)queries.size());
422 int errorCode = GetLastError();
424 return errorCode;
425 }
426 }
427 break;
428 }
429 }
430
431 // we might encounter errors during certain queries, and depending on the kind of error
432 // we might want to restart the transaction. So to prevent data loss, we only clean up when it's all done.
433 // This is done in calling functions DatabaseWorkerPool<T>::DirectCommitTransaction and TransactionTask::Execute,
434 // and not while iterating over every element.
435
437 return 0;
438}
439
440size_t MySQLConnection::EscapeString(char* to, const char* from, size_t length)
441{
442 return mysql_real_escape_string(m_Mysql, to, from, length);
443}
444
446{
447 mysql_ping(m_Mysql);
448}
449
451{
452 return mysql_errno(m_Mysql);
453}
454
456{
457 return m_Mutex.try_lock();
458}
459
461{
462 m_Mutex.unlock();
463}
464
466{
467 return mysql_get_server_version(m_Mysql);
468}
469
471{
472 ASSERT(index < m_stmts.size(), "Tried to access invalid prepared statement index %u (max index " SZFMTD ") on database `%s`, connection type: %s",
473 index, m_stmts.size(), m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
474 MySQLPreparedStatement* ret = m_stmts[index].get();
475 if (!ret)
476 TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement {} on database `{}`, connection type: {}.",
477 index, m_connectionInfo.database, (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
478
479 return ret;
480}
481
483{
484 // Check if specified query should be prepared on this connection
485 // i.e. don't prepare async statements on synchronous connections
486 // to save memory that will not be used.
487 if (!(m_connectionFlags & flags))
488 {
489 m_stmts[index].reset();
490 return;
491 }
492
493 MYSQL_STMT* stmt = mysql_stmt_init(m_Mysql);
494 if (!stmt)
495 {
496 TC_LOG_ERROR("sql.sql", "In mysql_stmt_init() id: {}, sql: \"{}\"", index, sql);
497 TC_LOG_ERROR("sql.sql", "{}", mysql_error(m_Mysql));
498 m_prepareError = true;
499 }
500 else
501 {
502 if (mysql_stmt_prepare(stmt, sql.c_str(), static_cast<unsigned long>(sql.size())))
503 {
504 TC_LOG_ERROR("sql.sql", "In mysql_stmt_prepare() id: {}, sql: \"{}\"", index, sql);
505 TC_LOG_ERROR("sql.sql", "{}", mysql_stmt_error(stmt));
506 mysql_stmt_close(stmt);
507 m_prepareError = true;
508 }
509 else
510 m_stmts[index] = std::make_unique<MySQLPreparedStatement>(reinterpret_cast<MySQLStmt*>(stmt), sql);
511 }
512}
513
515{
516 MySQLPreparedStatement* mysqlStmt = nullptr;
517 MySQLResult* result = nullptr;
518 uint64 rowCount = 0;
519 uint32 fieldCount = 0;
520
521 if (!_Query(stmt, &mysqlStmt, &result, &rowCount, &fieldCount))
522 return nullptr;
523
524 if (mysql_more_results(m_Mysql))
525 {
526 mysql_next_result(m_Mysql);
527 }
528 return new PreparedResultSet(mysqlStmt->GetSTMT(), result, rowCount, fieldCount);
529}
530
532{
533 switch (errNo)
534 {
535 case CR_SERVER_GONE_ERROR:
536 case CR_SERVER_LOST:
537 case CR_SERVER_LOST_EXTENDED:
538 {
539 if (m_Mysql)
540 {
541 TC_LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!");
542
543 mysql_close(m_Mysql);
544 m_Mysql = nullptr;
545 }
546 [[fallthrough]];
547 }
548 case CR_CONN_HOST_ERROR:
549 {
550 TC_LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server...");
551
552 m_reconnecting = true;
553
554 uint32 const lErrno = Open();
555 if (!lErrno)
556 {
557 // Don't remove 'this' pointer unless you want to skip loading all prepared statements...
558 if (!this->PrepareStatements())
559 {
560 TC_LOG_FATAL("sql.sql", "Could not re-prepare statements!");
561 std::this_thread::sleep_for(std::chrono::seconds(10));
562 ABORT();
563 }
564
565 TC_LOG_INFO("sql.sql", "Successfully reconnected to {} @{}:{} ({}).",
567 (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
568
569 m_reconnecting = false;
570 return true;
571 }
572
573 if ((--attempts) == 0)
574 {
575 // Shut down the server when the mysql server isn't
576 // reachable for some time
577 TC_LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, "
578 "terminating the server to prevent data corruption!");
579
580 // We could also initiate a shutdown through using std::raise(SIGTERM)
581 std::this_thread::sleep_for(std::chrono::seconds(10));
582 ABORT();
583 }
584 else
585 {
586 // It's possible this attempted reconnect throws 2006 at us.
587 // To prevent crazy recursive calls, sleep here.
588 std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds
589 return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive)
590 }
591 }
592
593 case ER_LOCK_DEADLOCK:
594 return false; // Implemented in TransactionTask::Execute and DatabaseWorkerPool<T>::DirectCommitTransaction
595 // Query related errors - skip query
596 case ER_WRONG_VALUE_COUNT:
597 case ER_DUP_ENTRY:
598 return false;
599
600 // Outdated table or database structure - terminate core
601 case ER_BAD_FIELD_ERROR:
602 case ER_NO_SUCH_TABLE:
603 TC_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.");
604 std::this_thread::sleep_for(std::chrono::seconds(10));
605 ABORT();
606 return false;
607 case ER_PARSE_ERROR:
608 TC_LOG_ERROR("sql.sql", "Error while parsing SQL. Core fix required.");
609 std::this_thread::sleep_for(std::chrono::seconds(10));
610 ABORT();
611 return false;
612 default:
613 TC_LOG_ERROR("sql.sql", "Unhandled MySQL errno {}. Unexpected behaviour possible.", errNo);
614 return false;
615 }
616}
uint8_t uint8
Definition Define.h:135
uint64_t uint64
Definition Define.h:132
uint32_t uint32
Definition Define.h:133
#define SZFMTD
Definition Define.h:123
uint16 flags
#define ABORT
Definition Errors.h:74
#define ASSERT
Definition Errors.h:68
#define TC_LOG_WARN(filterType__,...)
Definition Log.h:162
#define TC_LOG_DEBUG(filterType__,...)
Definition Log.h:156
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition Log.h:159
#define TC_LOG_FATAL(filterType__,...)
Definition Log.h:168
ConnectionFlags
@ CONNECTION_SYNCH
@ CONNECTION_ASYNC
std::remove_pointer_t< decltype(std::declval< MYSQL_BIND >().is_null)> MySQLBool
Definition MySQLHacks.h:32
@ SQL_ELEMENT_RAW
@ SQL_ELEMENT_PREPARED
uint32 getMSTime()
Definition Timer.h:33
uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
Definition Timer.h:40
bool Execute(char const *sql)
void PrepareStatement(uint32 index, std::string const &sql, ConnectionFlags flags)
MySQLPreparedStatement * GetPreparedStatement(uint32 index)
int ExecuteTransaction(std::shared_ptr< TransactionBase > transaction)
ProducerConsumerQueue< SQLOperation * > * m_queue
std::unique_ptr< DatabaseWorker > m_worker
Queue shared with other asynchronous connections.
MySQLConnectionInfo & m_connectionInfo
MySQL Handle.
virtual uint32 Open()
PreparedStatementContainer m_stmts
size_t EscapeString(char *to, const char *from, size_t length)
uint32 GetServerVersion() const
MySQLConnection(MySQLConnectionInfo &connInfo)
ResultSet * Query(char const *sql)
bool _Query(char const *sql, MySQLResult **pResult, MySQLField **pFields, uint64 *pRowCount, uint32 *pFieldCount)
ConnectionFlags m_connectionFlags
Connection info (used for logging)
bool _HandleMySQLErrno(uint32 errNo, uint8 attempts=5)
Was there any error while preparing statements?
void Unlock()
Called by parent databasepool. Will let other threads access this connection.
std::mutex m_Mutex
Connection flags (for preparing relevant statements)
bool m_prepareError
Are we reconnecting?
MySQLHandle * m_Mysql
Core worker task.
virtual ~MySQLConnection()
Constructor for asynchronous connections.
virtual void DoPrepareStatements()=0
bool m_reconnecting
PreparedStatements storage.
void BindParameters(PreparedStatementBase *stmt)
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition Util.cpp:56
std::string port_or_socket
MySQLConnectionInfo(std::string const &infoString)
SQLElementUnion element
PreparedStatementBase * stmt
char const * query