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 "IoContext.h"
21#include "Log.h"
22#include "MySQLHacks.h"
24#include "PreparedStatement.h"
25#include "QueryResult.h"
26#include "StringConvert.h"
27#include "Timer.h"
28#include "Transaction.h"
29#include "Util.h"
30#include <errmsg.h>
31#include "MySQLWorkaround.h"
32#include <mysqld_error.h>
33
34MySQLConnectionInfo::MySQLConnectionInfo(std::string const& infoString)
35{
36 std::vector<std::string_view> tokens = Trinity::Tokenize(infoString, ';', true);
37
38 if (tokens.size() != 5 && tokens.size() != 6)
39 return;
40
41 host.assign(tokens[0]);
42 port_or_socket.assign(tokens[1]);
43 user.assign(tokens[2]);
44 password.assign(tokens[3]);
45 database.assign(tokens[4]);
46
47 if (tokens.size() == 6)
48 ssl.assign(tokens[5]);
49}
50
52{
53 std::thread ThreadHandle;
54 boost::asio::executor_work_guard<Trinity::Asio::IoContext::Executor> WorkGuard;
55};
56
58m_reconnecting(false),
59m_prepareError(false),
60m_Mysql(nullptr),
61m_connectionInfo(connInfo),
62m_connectionFlags(connectionFlags)
63{
64}
65
70
72{
73 // Stop the worker thread before the statements are cleared
75 {
76 m_workerThread->WorkGuard.reset();
77 m_workerThread->ThreadHandle.join();
78 m_workerThread.reset();
79 }
80
81 m_stmts.clear();
82
83 if (m_Mysql)
84 {
85 mysql_close(m_Mysql);
86 m_Mysql = nullptr;
87 }
88}
89
91{
92 MYSQL *mysqlInit;
93 mysqlInit = mysql_init(nullptr);
94 if (!mysqlInit)
95 {
96 TC_LOG_ERROR("sql.sql", "Could not initialize Mysql connection to database `{}`", m_connectionInfo.database);
97 return CR_UNKNOWN_ERROR;
98 }
99
100 int port = 0;
101 char const* unix_socket = nullptr;
102 //unsigned int timeout = 10;
103
104 mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8mb4");
105 //mysql_options(mysqlInit, MYSQL_OPT_READ_TIMEOUT, (char const*)&timeout);
106 if (m_connectionInfo.host != ".")
107 {
108 port = Trinity::StringTo<int32>(m_connectionInfo.port_or_socket).value_or(0);
109 }
110 else // named pipe/unix socket option
111 {
112#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
113 unsigned int opt = MYSQL_PROTOCOL_PIPE;
114#else
115 m_connectionInfo.host = "localhost";
116 unix_socket = m_connectionInfo.port_or_socket.c_str();
117 unsigned int opt = MYSQL_PROTOCOL_SOCKET;
118#endif
119 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, (char const*)&opt);
120
121#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80000
122 /*
123 ensure connections over named pipes work for users authenticating with caching_sha2_password
124
125 If the mysql server is restarted, and you connect it using named pipe, the connection will fail and it will continue to fail until you connect it using tcp.
126 Source: https://bugs.mysql.com/bug.php?id=106852
127 */
128 MySQLBool geterverPublicKey = MySQLBool(1);
129 mysql_options(mysqlInit, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, (char const*)&geterverPublicKey);
130#endif
131 }
132
133 if (!m_connectionInfo.ssl.empty())
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<TransactionData> 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 if (!std::visit([this](auto&& data) { return this->Execute(TransactionData::ToExecutable(data)); }, itr->query))
400 {
401 TC_LOG_WARN("sql.sql", "Transaction aborted. {} queries not executed.", queries.size());
402 int errorCode = GetLastError();
404 return errorCode;
405 }
406 }
407
408 // we might encounter errors during certain queries, and depending on the kind of error
409 // we might want to restart the transaction. So to prevent data loss, we only clean up when it's all done.
410 // This is done in calling functions DatabaseWorkerPool<T>::DirectCommitTransaction and TransactionTask::Execute,
411 // and not while iterating over every element.
412
414 return 0;
415}
416
417size_t MySQLConnection::EscapeString(char* to, const char* from, size_t length)
418{
419 return mysql_real_escape_string(m_Mysql, to, from, length);
420}
421
423{
424 mysql_ping(m_Mysql);
425}
426
428{
429 return mysql_errno(m_Mysql);
430}
431
433{
434 boost::asio::executor_work_guard executorWorkGuard = boost::asio::make_work_guard(context->get_executor()); // construct guard before thread starts running
435
436 m_workerThread = std::make_unique<WorkerThread>(WorkerThread{
437 .ThreadHandle = std::thread([context] { context->run(); }),
438 .WorkGuard = std::move(executorWorkGuard)
439 });
440}
441
443{
444 if (m_workerThread)
445 return m_workerThread->ThreadHandle.get_id();
446
447 return {};
448}
449
451{
452 return m_Mutex.try_lock();
453}
454
456{
457 m_Mutex.unlock();
458}
459
461{
462 return mysql_get_server_version(m_Mysql);
463}
464
466{
467 ASSERT(index < m_stmts.size(), "Tried to access invalid prepared statement index %u (max index " SZFMTD ") on database `%s`, connection type: %s",
468 index, m_stmts.size(), m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
469 MySQLPreparedStatement* ret = m_stmts[index].get();
470 if (!ret)
471 TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement {} on database `{}`, connection type: {}.",
472 index, m_connectionInfo.database, (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
473
474 return ret;
475}
476
478{
479 // Check if specified query should be prepared on this connection
480 // i.e. don't prepare async statements on synchronous connections
481 // to save memory that will not be used.
482 if (!(m_connectionFlags & flags))
483 {
484 m_stmts[index].reset();
485 return;
486 }
487
488 MYSQL_STMT* stmt = mysql_stmt_init(m_Mysql);
489 if (!stmt)
490 {
491 TC_LOG_ERROR("sql.sql", "In mysql_stmt_init() id: {}, sql: \"{}\"", index, sql);
492 TC_LOG_ERROR("sql.sql", "{}", mysql_error(m_Mysql));
493 m_prepareError = true;
494 }
495 else
496 {
497 if (mysql_stmt_prepare(stmt, sql.data(), static_cast<unsigned long>(sql.size())))
498 {
499 TC_LOG_ERROR("sql.sql", "In mysql_stmt_prepare() id: {}, sql: \"{}\"", index, sql);
500 TC_LOG_ERROR("sql.sql", "{}", mysql_stmt_error(stmt));
501 mysql_stmt_close(stmt);
502 m_prepareError = true;
503 }
504 else
505 m_stmts[index] = std::make_unique<MySQLPreparedStatement>(reinterpret_cast<MySQLStmt*>(stmt), std::string(sql));
506 }
507}
508
510{
511 MySQLPreparedStatement* mysqlStmt = nullptr;
512 MySQLResult* result = nullptr;
513 uint64 rowCount = 0;
514 uint32 fieldCount = 0;
515
516 if (!_Query(stmt, &mysqlStmt, &result, &rowCount, &fieldCount))
517 return nullptr;
518
519 if (mysql_more_results(m_Mysql))
520 {
521 mysql_next_result(m_Mysql);
522 }
523 return new PreparedResultSet(mysqlStmt->GetSTMT(), result, rowCount, fieldCount);
524}
525
527{
528 switch (errNo)
529 {
530 case CR_SERVER_GONE_ERROR:
531 case CR_SERVER_LOST:
532 case CR_SERVER_LOST_EXTENDED:
533 {
534 if (m_Mysql)
535 {
536 TC_LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!");
537
538 mysql_close(m_Mysql);
539 m_Mysql = nullptr;
540 }
541 [[fallthrough]];
542 }
543 case CR_CONN_HOST_ERROR:
544 {
545 TC_LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server...");
546
547 m_reconnecting = true;
548
549 uint32 const lErrno = Open();
550 if (!lErrno)
551 {
552 // Don't remove 'this' pointer unless you want to skip loading all prepared statements...
553 if (!this->PrepareStatements())
554 {
555 TC_LOG_FATAL("sql.sql", "Could not re-prepare statements!");
556 std::this_thread::sleep_for(std::chrono::seconds(10));
557 ABORT();
558 }
559
560 TC_LOG_INFO("sql.sql", "Successfully reconnected to {} @{}:{} ({}).",
562 (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
563
564 m_reconnecting = false;
565 return true;
566 }
567
568 if ((--attempts) == 0)
569 {
570 // Shut down the server when the mysql server isn't
571 // reachable for some time
572 TC_LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, "
573 "terminating the server to prevent data corruption!");
574
575 // We could also initiate a shutdown through using std::raise(SIGTERM)
576 std::this_thread::sleep_for(std::chrono::seconds(10));
577 ABORT();
578 }
579 else
580 {
581 // It's possible this attempted reconnect throws 2006 at us.
582 // To prevent crazy recursive calls, sleep here.
583 std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds
584 return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive)
585 }
586 }
587
588 case ER_LOCK_DEADLOCK:
589 return false; // Implemented in TransactionTask::Execute and DatabaseWorkerPool<T>::DirectCommitTransaction
590 // Query related errors - skip query
591 case ER_WRONG_VALUE_COUNT:
592 case ER_DUP_ENTRY:
593 return false;
594
595 // Outdated table or database structure - terminate core
596 case ER_BAD_FIELD_ERROR:
597 case ER_NO_SUCH_TABLE:
598 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.");
599 std::this_thread::sleep_for(std::chrono::seconds(10));
600 ABORT();
601 return false;
602 case ER_PARSE_ERROR:
603 TC_LOG_ERROR("sql.sql", "Error while parsing SQL. Core fix required.");
604 std::this_thread::sleep_for(std::chrono::seconds(10));
605 ABORT();
606 return false;
607 default:
608 TC_LOG_ERROR("sql.sql", "Unhandled MySQL errno {}. Unexpected behaviour possible.", errNo);
609 return false;
610 }
611}
uint8_t uint8
Definition Define.h:156
uint64_t uint64
Definition Define.h:153
uint32_t uint32
Definition Define.h:154
#define SZFMTD
Definition Define.h:144
uint16 flags
#define ABORT
Definition Errors.h:87
#define ASSERT
Definition Errors.h:80
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_FATAL(filterType__, message__,...)
Definition Log.h:193
#define TC_LOG_INFO(filterType__, message__,...)
Definition Log.h:184
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:187
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::unique_ptr< WorkerThread > m_workerThread
Core worker thread.
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.
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)
bool m_reconnecting
Are we reconnecting?
void BindParameters(PreparedStatementBase *stmt)
Executor get_executor() noexcept
Definition IoContext.h:47
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition Util.cpp:57
std::string port_or_socket
MySQLConnectionInfo(std::string const &infoString)
boost::asio::executor_work_guard< Trinity::Asio::IoContext::Executor > WorkGuard
static PreparedStatementBase * ToExecutable(std::unique_ptr< PreparedStatementBase > const &stmt)
Definition Transaction.h:44