TrinityCore
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 "IoContext.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_Mysql(nullptr),
54m_connectionInfo(connInfo),
55m_connectionFlags(connectionFlags)
56{
57}
58
60{
61 Close();
62}
63
65{
66 // Stop the worker thread before the statements are cleared
68 {
69 m_workerThread->join();
70 m_workerThread.reset();
71 }
72
73 m_stmts.clear();
74
75 if (m_Mysql)
76 {
77 mysql_close(m_Mysql);
78 m_Mysql = nullptr;
79 }
80}
81
83{
84 MYSQL *mysqlInit;
85 mysqlInit = mysql_init(nullptr);
86 if (!mysqlInit)
87 {
88 TC_LOG_ERROR("sql.sql", "Could not initialize Mysql connection to database `{}`", m_connectionInfo.database);
89 return CR_UNKNOWN_ERROR;
90 }
91
92 int port;
93 char const* unix_socket;
94 //unsigned int timeout = 10;
95
96 mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8mb4");
97 //mysql_options(mysqlInit, MYSQL_OPT_READ_TIMEOUT, (char const*)&timeout);
98 #ifdef _WIN32
99 if (m_connectionInfo.host == ".") // named pipe use option (Windows)
100 {
101 unsigned int opt = MYSQL_PROTOCOL_PIPE;
102 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
103 port = 0;
104 unix_socket = nullptr;
105 }
106 else // generic case
107 {
108 port = atoi(m_connectionInfo.port_or_socket.c_str());
109 unix_socket = nullptr;
110 }
111 #else
112 if (m_connectionInfo.host == ".") // socket use option (Unix/Linux)
113 {
114 unsigned int opt = MYSQL_PROTOCOL_SOCKET;
115 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
116 m_connectionInfo.host = "localhost";
117 port = 0;
118 unix_socket = m_connectionInfo.port_or_socket.c_str();
119 }
120 else // generic case
121 {
122 port = atoi(m_connectionInfo.port_or_socket.c_str());
123 unix_socket = nullptr;
124 }
125 #endif
126
127 if (m_connectionInfo.ssl != "")
128 {
129#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80000
130 mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED;
131 if (m_connectionInfo.ssl == "ssl")
132 {
133 opt_use_ssl = SSL_MODE_REQUIRED;
134 }
135 mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl);
136#else
137 MySQLBool opt_use_ssl = MySQLBool(0);
138 if (m_connectionInfo.ssl == "ssl")
139 {
140 opt_use_ssl = MySQLBool(1);
141 }
142 mysql_options(mysqlInit, MYSQL_OPT_SSL_ENFORCE, (char const*)&opt_use_ssl);
143#endif
144 }
145
146 m_Mysql = reinterpret_cast<MySQLHandle*>(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(),
147 m_connectionInfo.password.c_str(), m_connectionInfo.database.c_str(), port, unix_socket, 0));
148
149 if (m_Mysql)
150 {
151 if (!m_reconnecting)
152 {
153 TC_LOG_INFO("sql.sql", "MySQL client library: {}", mysql_get_client_info());
154 TC_LOG_INFO("sql.sql", "MySQL server ver: {} ", mysql_get_server_info(m_Mysql));
155 // MySQL version above 5.1 IS required in both client and server and there is no known issue with different versions above 5.1
156 // if (mysql_get_server_version(m_Mysql) != mysql_get_client_version())
157 // TC_LOG_INFO("sql.sql", "[WARNING] MySQL client/server version mismatch; may conflict with behaviour of prepared statements.");
158 }
159
160 TC_LOG_INFO("sql.sql", "Connected to MySQL database at {}", m_connectionInfo.host);
161 mysql_autocommit(m_Mysql, 1);
162
163 // set connection properties to UTF8 to properly handle locales for different
164 // server configs - core sends data in UTF8, so MySQL must expect UTF8 too
165 mysql_set_character_set(m_Mysql, "utf8mb4");
166 return 0;
167 }
168 else
169 {
170 TC_LOG_ERROR("sql.sql", "Could not connect to MySQL database at {}: {}", m_connectionInfo.host, mysql_error(mysqlInit));
171 uint32 errorCode = mysql_errno(mysqlInit);
172 mysql_close(mysqlInit);
173 return errorCode;
174 }
175}
176
178{
180 return !m_prepareError;
181}
182
183bool MySQLConnection::Execute(char const* sql)
184{
185 if (!m_Mysql)
186 return false;
187
188 {
189 uint32 _s = getMSTime();
190
191 if (mysql_query(m_Mysql, sql))
192 {
193 uint32 lErrno = mysql_errno(m_Mysql);
194
195 TC_LOG_INFO("sql.sql", "SQL: {}", sql);
196 TC_LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
197
198 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
199 return Execute(sql); // Try again
200
201 return false;
202 }
203 else
204 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
205 }
206
207 return true;
208}
209
210static auto mysql_bind_param_no_deprecated(MYSQL_STMT* stmt, MYSQL_BIND* bnd)
211{
212#if TRINITY_COMPILER == TRINITY_COMPILER_GNU
213#pragma GCC diagnostic push
214#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
215#else
216#pragma warning(push)
217#pragma warning(disable: 4996)
218#endif
219
220 return mysql_stmt_bind_param(stmt, bnd);
221
222#if TRINITY_COMPILER == TRINITY_COMPILER_GNU
223#pragma GCC diagnostic pop
224#else
225#pragma warning(pop)
226#endif
227}
228
230{
231 if (!m_Mysql)
232 return false;
233
234 uint32 index = stmt->GetIndex();
235
237 ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
238
239 m_mStmt->BindParameters(stmt);
240
241 MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
242 MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
243
244 uint32 _s = getMSTime();
245
246 if (mysql_bind_param_no_deprecated(msql_STMT, msql_BIND))
247 {
248 uint32 lErrno = mysql_errno(m_Mysql);
249 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
250
251 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
252 return Execute(stmt); // Try again
253
254 m_mStmt->ClearParameters();
255 return false;
256 }
257
258 if (mysql_stmt_execute(msql_STMT))
259 {
260 uint32 lErrno = mysql_errno(m_Mysql);
261 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
262
263 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
264 return Execute(stmt); // Try again
265
266 m_mStmt->ClearParameters();
267 return false;
268 }
269
270 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
271
272 m_mStmt->ClearParameters();
273 return true;
274}
275
276bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement** mysqlStmt, MySQLResult** pResult, uint64* pRowCount, uint32* pFieldCount)
277{
278 if (!m_Mysql)
279 return false;
280
281 uint32 index = stmt->GetIndex();
282
284 ASSERT(m_mStmt); // Can only be null if preparation failed, server side error or bad query
285
286 m_mStmt->BindParameters(stmt);
287 *mysqlStmt = m_mStmt;
288
289 MYSQL_STMT* msql_STMT = m_mStmt->GetSTMT();
290 MYSQL_BIND* msql_BIND = m_mStmt->GetBind();
291
292 uint32 _s = getMSTime();
293
294 if (mysql_bind_param_no_deprecated(msql_STMT, msql_BIND))
295 {
296 uint32 lErrno = mysql_errno(m_Mysql);
297 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}", 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 if (mysql_stmt_execute(msql_STMT))
307 {
308 uint32 lErrno = mysql_errno(m_Mysql);
309 TC_LOG_ERROR("sql.sql", "SQL(p): {}\n [ERROR]: [{}] {}",
310 m_mStmt->getQueryString(), lErrno, mysql_stmt_error(msql_STMT));
311
312 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
313 return _Query(stmt, mysqlStmt, pResult, pRowCount, pFieldCount); // Try again
314
315 m_mStmt->ClearParameters();
316 return false;
317 }
318
319 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL(p): {}", getMSTimeDiff(_s, getMSTime()), m_mStmt->getQueryString());
320
321 m_mStmt->ClearParameters();
322
323 *pResult = reinterpret_cast<MySQLResult*>(mysql_stmt_result_metadata(msql_STMT));
324 *pRowCount = mysql_stmt_num_rows(msql_STMT);
325 *pFieldCount = mysql_stmt_field_count(msql_STMT);
326
327 return true;
328}
329
331{
332 if (!sql)
333 return nullptr;
334
335 MySQLResult* result = nullptr;
336 MySQLField* fields = nullptr;
337 uint64 rowCount = 0;
338 uint32 fieldCount = 0;
339
340 if (!_Query(sql, &result, &fields, &rowCount, &fieldCount))
341 return nullptr;
342
343 return new ResultSet(result, fields, rowCount, fieldCount);
344}
345
346bool MySQLConnection::_Query(const char* sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount)
347{
348 if (!m_Mysql)
349 return false;
350
351 {
352 uint32 _s = getMSTime();
353
354 if (mysql_query(m_Mysql, sql))
355 {
356 uint32 lErrno = mysql_errno(m_Mysql);
357 TC_LOG_INFO("sql.sql", "SQL: {}", sql);
358 TC_LOG_ERROR("sql.sql", "[{}] {}", lErrno, mysql_error(m_Mysql));
359
360 if (_HandleMySQLErrno(lErrno)) // If it returns true, an error was handled successfully (i.e. reconnection)
361 return _Query(sql, pResult, pFields, pRowCount, pFieldCount); // We try again
362
363 return false;
364 }
365 else
366 TC_LOG_DEBUG("sql.sql", "[{} ms] SQL: {}", getMSTimeDiff(_s, getMSTime()), sql);
367
368 *pResult = reinterpret_cast<MySQLResult*>(mysql_store_result(m_Mysql));
369 *pRowCount = mysql_affected_rows(m_Mysql);
370 *pFieldCount = mysql_field_count(m_Mysql);
371 }
372
373 if (!*pResult )
374 return false;
375
376 if (!*pRowCount)
377 {
378 mysql_free_result(*pResult);
379 return false;
380 }
381
382 *pFields = reinterpret_cast<MySQLField*>(mysql_fetch_fields(*pResult));
383
384 return true;
385}
386
388{
389 Execute("START TRANSACTION");
390}
391
393{
394 Execute("ROLLBACK");
395}
396
398{
399 Execute("COMMIT");
400}
401
402int MySQLConnection::ExecuteTransaction(std::shared_ptr<TransactionBase> transaction)
403{
404 std::vector<TransactionData> const& queries = transaction->m_queries;
405 if (queries.empty())
406 return -1;
407
409
410 for (auto itr = queries.begin(); itr != queries.end(); ++itr)
411 {
412 if (!std::visit([this](auto&& data) { return this->Execute(TransactionData::ToExecutable(data)); }, itr->query))
413 {
414 TC_LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", queries.size());
415 int errorCode = GetLastError();
417 return errorCode;
418 }
419 }
420
421 // we might encounter errors during certain queries, and depending on the kind of error
422 // we might want to restart the transaction. So to prevent data loss, we only clean up when it's all done.
423 // This is done in calling functions DatabaseWorkerPool<T>::DirectCommitTransaction and TransactionTask::Execute,
424 // and not while iterating over every element.
425
427 return 0;
428}
429
430size_t MySQLConnection::EscapeString(char* to, const char* from, size_t length)
431{
432 return mysql_real_escape_string(m_Mysql, to, from, length);
433}
434
436{
437 mysql_ping(m_Mysql);
438}
439
441{
442 return mysql_errno(m_Mysql);
443}
444
446{
447 m_workerThread = std::make_unique<std::thread>([context]
448 {
449 boost::asio::executor_work_guard executorWorkGuard = boost::asio::make_work_guard(context->get_executor());
450
451 context->run();
452 });
453}
454
456{
457 if (m_workerThread)
458 return m_workerThread->get_id();
459
460 return {};
461}
462
464{
465 return m_Mutex.try_lock();
466}
467
469{
470 m_Mutex.unlock();
471}
472
474{
475 return mysql_get_server_version(m_Mysql);
476}
477
479{
480 ASSERT(index < m_stmts.size(), "Tried to access invalid prepared statement index %u (max index " SZFMTD ") on database `%s`, connection type: %s",
481 index, m_stmts.size(), m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
482 MySQLPreparedStatement* ret = m_stmts[index].get();
483 if (!ret)
484 TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement {} on database `{}`, connection type: {}.",
485 index, m_connectionInfo.database, (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
486
487 return ret;
488}
489
491{
492 // Check if specified query should be prepared on this connection
493 // i.e. don't prepare async statements on synchronous connections
494 // to save memory that will not be used.
495 if (!(m_connectionFlags & flags))
496 {
497 m_stmts[index].reset();
498 return;
499 }
500
501 MYSQL_STMT* stmt = mysql_stmt_init(m_Mysql);
502 if (!stmt)
503 {
504 TC_LOG_ERROR("sql.sql", "In mysql_stmt_init() id: {}, sql: \"{}\"", index, sql);
505 TC_LOG_ERROR("sql.sql", "{}", mysql_error(m_Mysql));
506 m_prepareError = true;
507 }
508 else
509 {
510 if (mysql_stmt_prepare(stmt, sql.data(), static_cast<unsigned long>(sql.size())))
511 {
512 TC_LOG_ERROR("sql.sql", "In mysql_stmt_prepare() id: {}, sql: \"{}\"", index, sql);
513 TC_LOG_ERROR("sql.sql", "{}", mysql_stmt_error(stmt));
514 mysql_stmt_close(stmt);
515 m_prepareError = true;
516 }
517 else
518 m_stmts[index] = std::make_unique<MySQLPreparedStatement>(reinterpret_cast<MySQLStmt*>(stmt), std::string(sql));
519 }
520}
521
523{
524 MySQLPreparedStatement* mysqlStmt = nullptr;
525 MySQLResult* result = nullptr;
526 uint64 rowCount = 0;
527 uint32 fieldCount = 0;
528
529 if (!_Query(stmt, &mysqlStmt, &result, &rowCount, &fieldCount))
530 return nullptr;
531
532 if (mysql_more_results(m_Mysql))
533 {
534 mysql_next_result(m_Mysql);
535 }
536 return new PreparedResultSet(mysqlStmt->GetSTMT(), result, rowCount, fieldCount);
537}
538
540{
541 switch (errNo)
542 {
543 case CR_SERVER_GONE_ERROR:
544 case CR_SERVER_LOST:
545 case CR_SERVER_LOST_EXTENDED:
546 {
547 if (m_Mysql)
548 {
549 TC_LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!");
550
551 mysql_close(m_Mysql);
552 m_Mysql = nullptr;
553 }
554 [[fallthrough]];
555 }
556 case CR_CONN_HOST_ERROR:
557 {
558 TC_LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server...");
559
560 m_reconnecting = true;
561
562 uint32 const lErrno = Open();
563 if (!lErrno)
564 {
565 // Don't remove 'this' pointer unless you want to skip loading all prepared statements...
566 if (!this->PrepareStatements())
567 {
568 TC_LOG_FATAL("sql.sql", "Could not re-prepare statements!");
569 std::this_thread::sleep_for(std::chrono::seconds(10));
570 ABORT();
571 }
572
573 TC_LOG_INFO("sql.sql", "Successfully reconnected to {} @{}:{} ({}).",
575 (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
576
577 m_reconnecting = false;
578 return true;
579 }
580
581 if ((--attempts) == 0)
582 {
583 // Shut down the server when the mysql server isn't
584 // reachable for some time
585 TC_LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, "
586 "terminating the server to prevent data corruption!");
587
588 // We could also initiate a shutdown through using std::raise(SIGTERM)
589 std::this_thread::sleep_for(std::chrono::seconds(10));
590 ABORT();
591 }
592 else
593 {
594 // It's possible this attempted reconnect throws 2006 at us.
595 // To prevent crazy recursive calls, sleep here.
596 std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds
597 return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive)
598 }
599 }
600
601 case ER_LOCK_DEADLOCK:
602 return false; // Implemented in TransactionTask::Execute and DatabaseWorkerPool<T>::DirectCommitTransaction
603 // Query related errors - skip query
604 case ER_WRONG_VALUE_COUNT:
605 case ER_DUP_ENTRY:
606 return false;
607
608 // Outdated table or database structure - terminate core
609 case ER_BAD_FIELD_ERROR:
610 case ER_NO_SUCH_TABLE:
611 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.");
612 std::this_thread::sleep_for(std::chrono::seconds(10));
613 ABORT();
614 return false;
615 case ER_PARSE_ERROR:
616 TC_LOG_ERROR("sql.sql", "Error while parsing SQL. Core fix required.");
617 std::this_thread::sleep_for(std::chrono::seconds(10));
618 ABORT();
619 return false;
620 default:
621 TC_LOG_ERROR("sql.sql", "Unhandled MySQL errno {}. Unexpected behaviour possible.", errNo);
622 return false;
623 }
624}
uint8_t uint8
Definition: Define.h:144
uint64_t uint64
Definition: Define.h:141
uint32_t uint32
Definition: Define.h:142
#define SZFMTD
Definition: Define.h:132
uint16 flags
Definition: DisableMgr.cpp:49
#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
static auto mysql_bind_param_no_deprecated(MYSQL_STMT *stmt, MYSQL_BIND *bnd)
ConnectionFlags
@ CONNECTION_ASYNC
std::remove_pointer_t< decltype(std::declval< MYSQL_BIND >().is_null)> MySQLBool
Definition: MySQLHacks.h:32
uint32 getMSTime()
Definition: Timer.h:33
uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
Definition: Timer.h:40
std::thread::id GetWorkerThreadId() const
bool Execute(char const *sql)
void PrepareStatement(uint32 index, std::string_view sql, ConnectionFlags flags)
MySQLPreparedStatement * GetPreparedStatement(uint32 index)
int ExecuteTransaction(std::shared_ptr< TransactionBase > transaction)
MySQLConnectionInfo & m_connectionInfo
Connection info (used for logging)
PreparedStatementContainer m_stmts
PreparedStatements storage.
size_t EscapeString(char *to, const char *from, size_t length)
uint32 GetServerVersion() const
MySQLConnection(MySQLConnectionInfo &connInfo, ConnectionFlags connectionFlags)
ResultSet * Query(char const *sql)
bool _Query(char const *sql, MySQLResult **pResult, MySQLField **pFields, uint64 *pRowCount, uint32 *pFieldCount)
ConnectionFlags m_connectionFlags
Connection flags (for preparing relevant statements)
bool _HandleMySQLErrno(uint32 errNo, uint8 attempts=5)
void Unlock()
Called by parent databasepool. Will let other threads access this connection.
std::mutex m_Mutex
bool m_prepareError
Was there any error while preparing statements?
MySQLHandle * m_Mysql
MySQL Handle.
virtual ~MySQLConnection()
virtual void DoPrepareStatements()=0
void StartWorkerThread(Trinity::Asio::IoContext *context)
std::unique_ptr< std::thread > m_workerThread
Core worker thread.
bool m_reconnecting
Are we reconnecting?
std::string getQueryString() const
void BindParameters(PreparedStatementBase *stmt)
Executor get_executor() noexcept
Definition: IoContext.h:47
std::size_t run()
Definition: IoContext.h:40
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)
static PreparedStatementBase * ToExecutable(std::unique_ptr< PreparedStatementBase > const &stmt)
Definition: Transaction.h:38