1#include "connection.h"
2
3#include <engine/server/databases/connection_pool.h>
4
5#if defined(CONF_MYSQL)
6#include <base/dbg.h>
7#include <base/log.h>
8#include <base/mem.h>
9#include <base/sphore.h>
10#include <base/str.h>
11
12#include <mysql.h>
13
14#include <atomic>
15#include <memory>
16#include <vector>
17
18// MySQL >= 8.0.1 removed my_bool, 8.0.2 accidentally reintroduced it: https://bugs.mysql.com/bug.php?id=87337
19#if !defined(LIBMARIADB) && MYSQL_VERSION_ID >= 80001 && MYSQL_VERSION_ID != 80002
20typedef bool my_bool;
21#endif
22
23enum
24{
25 MYSQLSTATE_UNINITIALIZED,
26 MYSQLSTATE_INITIALIZED,
27 MYSQLSTATE_SHUTTINGDOWN,
28};
29
30static std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED};
31static std::atomic_int g_MysqlNumConnections;
32
33bool MysqlAvailable()
34{
35 return true;
36}
37
38int MysqlInit()
39{
40 dbg_assert(mysql_thread_safe(), "MySQL library without thread safety");
41 dbg_assert(g_MysqlState == MYSQLSTATE_UNINITIALIZED, "double MySQL initialization");
42 if(mysql_library_init(argc: 0, argv: nullptr, groups: nullptr))
43 {
44 return 1;
45 }
46 int Uninitialized = MYSQLSTATE_UNINITIALIZED;
47 bool Swapped = g_MysqlState.compare_exchange_strong(i1&: Uninitialized, i2: MYSQLSTATE_INITIALIZED);
48 (void)Swapped;
49 dbg_assert(Swapped, "MySQL double initialization");
50 return 0;
51}
52
53void MysqlUninit()
54{
55 int Initialized = MYSQLSTATE_INITIALIZED;
56 bool Swapped = g_MysqlState.compare_exchange_strong(i1&: Initialized, i2: MYSQLSTATE_SHUTTINGDOWN);
57 (void)Swapped;
58 dbg_assert(Swapped, "double MySQL free or free without initialization");
59 int Counter = g_MysqlNumConnections;
60 if(Counter != 0)
61 {
62 dbg_msg(sys: "mysql", fmt: "can't deinitialize, connections remaining: %d", Counter);
63 return;
64 }
65 mysql_library_end();
66}
67
68class CMysqlConnection : public IDbConnection
69{
70public:
71 explicit CMysqlConnection(CMysqlConfig Config);
72 ~CMysqlConnection() override;
73 void Print(const char *pMode) override;
74
75 const char *BinaryCollate() const override { return "utf8mb4_bin"; }
76 void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) override;
77 const char *InsertTimestampAsUtc() const override { return "?"; }
78 const char *CollateNocase() const override { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
79 const char *InsertIgnore() const override { return "INSERT IGNORE"; }
80 const char *Random() const override { return "RAND()"; }
81 const char *False() const override { return "FALSE"; }
82 const char *True() const override { return "TRUE"; }
83
84 bool Connect(char *pError, int ErrorSize) override;
85 void Disconnect() override;
86
87 bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize) override;
88
89 void BindString(int Idx, const char *pString) override;
90 void BindBlob(int Idx, unsigned char *pBlob, int Size) override;
91 void BindInt(int Idx, int Value) override;
92 void BindInt64(int Idx, int64_t Value) override;
93 void BindFloat(int Idx, float Value) override;
94 void BindNull(int Idx) override;
95
96 void Print() override {}
97 bool Step(bool *pEnd, char *pError, int ErrorSize) override;
98 bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) override;
99
100 bool IsNull(int Col) override;
101 float GetFloat(int Col) override;
102 int GetInt(int Col) override;
103 int64_t GetInt64(int Col) override;
104 void GetString(int Col, char *pBuffer, int BufferSize) override;
105 int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) override;
106
107 bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
108
109private:
110 class CStmtDeleter
111 {
112 public:
113 void operator()(MYSQL_STMT *pStmt) const;
114 };
115
116 char m_aErrorDetail[128];
117 void StoreErrorMysql(const char *pContext);
118 void StoreErrorStmt(const char *pContext);
119 bool ConnectImpl();
120 bool PrepareAndExecuteStatement(const char *pStmt);
121
122 union UParameterExtra
123 {
124 int m_Int;
125 int64_t m_Int64;
126 unsigned long m_UnsignedLong;
127 float m_Float;
128 };
129
130 bool m_NewQuery = false;
131 bool m_HaveConnection = false;
132 MYSQL m_Mysql;
133 std::unique_ptr<MYSQL_STMT, CStmtDeleter> m_pStmt = nullptr;
134 std::vector<MYSQL_BIND> m_vStmtParameters;
135 std::vector<UParameterExtra> m_vStmtParameterExtras;
136
137 // copy of m_Config vars
138 CMysqlConfig m_Config;
139
140 std::atomic_bool m_InUse;
141};
142
143void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const
144{
145 mysql_stmt_close(stmt: pStmt);
146}
147
148CMysqlConnection::CMysqlConnection(CMysqlConfig Config) :
149 IDbConnection(Config.m_aPrefix),
150 m_Config(Config),
151 m_InUse(false)
152{
153 g_MysqlNumConnections += 1;
154 dbg_assert(g_MysqlState == MYSQLSTATE_INITIALIZED, "MySQL library not in initialized state");
155
156 m_aErrorDetail[0] = '\0';
157 mem_zero(block: &m_Mysql, size: sizeof(m_Mysql));
158 mysql_init(mysql: &m_Mysql);
159}
160
161CMysqlConnection::~CMysqlConnection()
162{
163 mysql_close(sock: &m_Mysql);
164 g_MysqlNumConnections -= 1;
165}
166
167void CMysqlConnection::StoreErrorMysql(const char *pContext)
168{
169 str_format(buffer: m_aErrorDetail, buffer_size: sizeof(m_aErrorDetail), format: "(%s:mysql:%d): %s", pContext, mysql_errno(mysql: &m_Mysql), mysql_error(mysql: &m_Mysql));
170}
171
172void CMysqlConnection::StoreErrorStmt(const char *pContext)
173{
174 str_format(buffer: m_aErrorDetail, buffer_size: sizeof(m_aErrorDetail), format: "(%s:stmt:%d): %s", pContext, mysql_stmt_errno(stmt: m_pStmt.get()), mysql_stmt_error(stmt: m_pStmt.get()));
175}
176
177bool CMysqlConnection::PrepareAndExecuteStatement(const char *pStmt)
178{
179 if(mysql_stmt_prepare(stmt: m_pStmt.get(), query: pStmt, length: str_length(str: pStmt)))
180 {
181 StoreErrorStmt(pContext: "prepare");
182 return false;
183 }
184 if(mysql_stmt_execute(stmt: m_pStmt.get()))
185 {
186 StoreErrorStmt(pContext: "execute");
187 return false;
188 }
189 return true;
190}
191
192void CMysqlConnection::Print(const char *pMode)
193{
194 log_info("server",
195 "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
196 pMode, m_Config.m_aDatabase, GetPrefix(), m_Config.m_aUser, m_Config.m_aIp, m_Config.m_Port);
197}
198
199void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
200{
201 str_format(buffer: aBuf, buffer_size: BufferSize, format: "UNIX_TIMESTAMP(%s)", pTimestamp);
202}
203
204bool CMysqlConnection::Connect(char *pError, int ErrorSize)
205{
206 dbg_assert(!m_InUse.exchange(true), "Tried connecting while the connection is in use");
207
208 m_NewQuery = true;
209 if(!ConnectImpl())
210 {
211 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
212 m_InUse.store(i: false);
213 return false;
214 }
215 return true;
216}
217
218bool CMysqlConnection::ConnectImpl()
219{
220 if(m_HaveConnection)
221 {
222 if(m_pStmt && mysql_stmt_field_count(stmt: m_pStmt.get()) > 0 && mysql_stmt_free_result(stmt: m_pStmt.get()))
223 {
224 StoreErrorStmt(pContext: "free_result");
225 dbg_msg(sys: "mysql", fmt: "can't free last result %s", m_aErrorDetail);
226 }
227 if(!mysql_select_db(mysql: &m_Mysql, db: m_Config.m_aDatabase))
228 {
229 // Success.
230 return true;
231 }
232 StoreErrorMysql(pContext: "select_db");
233 dbg_msg(sys: "mysql", fmt: "ping error, trying to reconnect %s", m_aErrorDetail);
234 mysql_close(sock: &m_Mysql);
235 mem_zero(block: &m_Mysql, size: sizeof(m_Mysql));
236 mysql_init(mysql: &m_Mysql);
237 }
238
239 m_pStmt = nullptr;
240 unsigned int OptConnectTimeout = 60;
241 unsigned int OptReadTimeout = 60;
242 unsigned int OptWriteTimeout = 120;
243 my_bool OptReconnect = true;
244 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_CONNECT_TIMEOUT, arg: &OptConnectTimeout);
245 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_READ_TIMEOUT, arg: &OptReadTimeout);
246 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_WRITE_TIMEOUT, arg: &OptWriteTimeout);
247 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_RECONNECT, arg: &OptReconnect);
248 mysql_options(mysql: &m_Mysql, option: MYSQL_SET_CHARSET_NAME, arg: "utf8mb4");
249 if(m_Config.m_aBindaddr[0] != '\0')
250 {
251 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_BIND, arg: m_Config.m_aBindaddr);
252 }
253
254 // SSL support
255 int ClientFlags = CLIENT_IGNORE_SIGPIPE;
256 if(m_Config.m_UseSsl)
257 {
258 // Enable SSL, e.g. required by servers with require_secure_transport enabled.
259 // If no CA file is given, the server certificate is not verified.
260 if(m_Config.m_aSslKey[0])
261 {
262 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_SSL_KEY, arg: m_Config.m_aSslKey);
263 }
264 if(m_Config.m_aSslCert[0])
265 {
266 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_SSL_CERT, arg: m_Config.m_aSslCert);
267 }
268 if(m_Config.m_aSslCa[0])
269 {
270 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_SSL_CA, arg: m_Config.m_aSslCa);
271#if defined(MARIADB_VERSION_ID)
272 my_bool OptVerifyServerCert = 1;
273 mysql_options(mysql: &m_Mysql, option: MYSQL_OPT_SSL_VERIFY_SERVER_CERT, arg: &OptVerifyServerCert);
274#else
275 // MYSQL_OPT_SSL_VERIFY_SERVER_CERT is deprecated/removed in MySQL 8.0, use MYSQL_OPT_SSL_MODE instead
276 unsigned int OptSslMode = SSL_MODE_VERIFY_IDENTITY;
277 mysql_options(&m_Mysql, MYSQL_OPT_SSL_MODE, &OptSslMode);
278#endif
279 }
280 ClientFlags |= CLIENT_SSL;
281 }
282
283 if(!mysql_real_connect(mysql: &m_Mysql, host: m_Config.m_aIp, user: m_Config.m_aUser, passwd: m_Config.m_aPass, db: nullptr, port: m_Config.m_Port, unix_socket: nullptr, clientflag: ClientFlags))
284 {
285 StoreErrorMysql(pContext: "real_connect");
286 return false;
287 }
288
289 // Check SSL status
290 if(m_Config.m_UseSsl)
291 {
292 const char *pSslCipher = mysql_get_ssl_cipher(mysql: &m_Mysql);
293 if(!pSslCipher)
294 {
295 str_copy(dst: m_aErrorDetail, src: "(ssl_cipher): connection not encrypted despite m_UseSsl (server ssl disabled?)", dst_size: sizeof(m_aErrorDetail));
296 mysql_close(sock: &m_Mysql);
297 mem_zero(block: &m_Mysql, size: sizeof(m_Mysql));
298 mysql_init(mysql: &m_Mysql);
299 return false;
300 }
301 dbg_msg(sys: "mysql", fmt: "using ssl cipher: %s", pSslCipher);
302 }
303
304 m_HaveConnection = true;
305
306 m_pStmt = std::unique_ptr<MYSQL_STMT, CStmtDeleter>(mysql_stmt_init(mysql: &m_Mysql));
307
308 // Apparently MYSQL_SET_CHARSET_NAME is not enough
309 if(!PrepareAndExecuteStatement(pStmt: "SET CHARACTER SET utf8mb4"))
310 {
311 return false;
312 }
313
314 if(m_Config.m_Setup)
315 {
316 char aCreateDatabase[1024];
317 // create database
318 str_format(buffer: aCreateDatabase, buffer_size: sizeof(aCreateDatabase), format: "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_Config.m_aDatabase);
319 if(!PrepareAndExecuteStatement(pStmt: aCreateDatabase))
320 {
321 return false;
322 }
323 }
324
325 // Connect to specific database
326 if(mysql_select_db(mysql: &m_Mysql, db: m_Config.m_aDatabase))
327 {
328 StoreErrorMysql(pContext: "select_db");
329 return false;
330 }
331
332 if(m_Config.m_Setup)
333 {
334 char aCreateRace[1024];
335 char aCreateTeamrace[1024];
336 char aCreateMaps[1024];
337 char aCreateSaves[1024];
338 char aCreatePoints[1024];
339 FormatCreateRace(aBuf: aCreateRace, BufferSize: sizeof(aCreateRace), /* Backup */ false);
340 FormatCreateTeamrace(aBuf: aCreateTeamrace, BufferSize: sizeof(aCreateTeamrace), pIdType: "VARBINARY(16)", /* Backup */ false);
341 FormatCreateMaps(aBuf: aCreateMaps, BufferSize: sizeof(aCreateMaps));
342 FormatCreateSaves(aBuf: aCreateSaves, BufferSize: sizeof(aCreateSaves), /* Backup */ false);
343 FormatCreatePoints(aBuf: aCreatePoints, BufferSize: sizeof(aCreatePoints));
344
345 if(!PrepareAndExecuteStatement(pStmt: aCreateRace) ||
346 !PrepareAndExecuteStatement(pStmt: aCreateTeamrace) ||
347 !PrepareAndExecuteStatement(pStmt: aCreateMaps) ||
348 !PrepareAndExecuteStatement(pStmt: aCreateSaves) ||
349 !PrepareAndExecuteStatement(pStmt: aCreatePoints))
350 {
351 return false;
352 }
353 m_Config.m_Setup = false;
354 }
355 dbg_msg(sys: "mysql", fmt: "connection established");
356 return true;
357}
358
359void CMysqlConnection::Disconnect()
360{
361 m_InUse.store(i: false);
362}
363
364bool CMysqlConnection::PrepareStatement(const char *pStmt, char *pError, int ErrorSize)
365{
366 if(mysql_stmt_prepare(stmt: m_pStmt.get(), query: pStmt, length: str_length(str: pStmt)))
367 {
368 StoreErrorStmt(pContext: "prepare");
369 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
370 return false;
371 }
372 m_NewQuery = true;
373 unsigned NumParameters = mysql_stmt_param_count(stmt: m_pStmt.get());
374 m_vStmtParameters.resize(sz: NumParameters);
375 m_vStmtParameterExtras.resize(sz: NumParameters);
376 if(NumParameters)
377 {
378 mem_zero(block: m_vStmtParameters.data(), size: sizeof(m_vStmtParameters[0]) * m_vStmtParameters.size());
379 mem_zero(block: m_vStmtParameterExtras.data(), size: sizeof(m_vStmtParameterExtras[0]) * m_vStmtParameterExtras.size());
380 }
381 return true;
382}
383
384void CMysqlConnection::BindString(int Idx, const char *pString)
385{
386 m_NewQuery = true;
387 Idx -= 1;
388 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindString: index out of bounds: %d", Idx);
389
390 int Length = str_length(str: pString);
391 m_vStmtParameterExtras[Idx].m_UnsignedLong = Length;
392 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
393 pParam->buffer_type = MYSQL_TYPE_STRING;
394 pParam->buffer = (void *)pString;
395 pParam->buffer_length = Length + 1;
396 pParam->length = &m_vStmtParameterExtras[Idx].m_UnsignedLong;
397 pParam->is_null = nullptr;
398 pParam->is_unsigned = false;
399 pParam->error = nullptr;
400}
401
402void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
403{
404 m_NewQuery = true;
405 Idx -= 1;
406 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindBlob: index out of bounds: %d", Idx);
407
408 m_vStmtParameterExtras[Idx].m_UnsignedLong = Size;
409 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
410 pParam->buffer_type = MYSQL_TYPE_BLOB;
411 pParam->buffer = pBlob;
412 pParam->buffer_length = Size;
413 pParam->length = &m_vStmtParameterExtras[Idx].m_UnsignedLong;
414 pParam->is_null = nullptr;
415 pParam->is_unsigned = false;
416 pParam->error = nullptr;
417}
418
419void CMysqlConnection::BindInt(int Idx, int Value)
420{
421 m_NewQuery = true;
422 Idx -= 1;
423 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindInt: index out of bounds: %d", Idx);
424
425 m_vStmtParameterExtras[Idx].m_Int = Value;
426 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
427 pParam->buffer_type = MYSQL_TYPE_LONG;
428 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Int;
429 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Int);
430 pParam->length = nullptr;
431 pParam->is_null = nullptr;
432 pParam->is_unsigned = false;
433 pParam->error = nullptr;
434}
435
436void CMysqlConnection::BindInt64(int Idx, int64_t Value)
437{
438 m_NewQuery = true;
439 Idx -= 1;
440 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindInt64: index out of bounds: %d", Idx);
441
442 m_vStmtParameterExtras[Idx].m_Int64 = Value;
443 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
444 pParam->buffer_type = MYSQL_TYPE_LONGLONG;
445 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Int64;
446 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Int64);
447 pParam->length = nullptr;
448 pParam->is_null = nullptr;
449 pParam->is_unsigned = false;
450 pParam->error = nullptr;
451}
452
453void CMysqlConnection::BindFloat(int Idx, float Value)
454{
455 m_NewQuery = true;
456 Idx -= 1;
457 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindFloat: index out of bounds: %d", Idx);
458
459 m_vStmtParameterExtras[Idx].m_Float = Value;
460 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
461 pParam->buffer_type = MYSQL_TYPE_FLOAT;
462 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Float;
463 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Float);
464 pParam->length = nullptr;
465 pParam->is_null = nullptr;
466 pParam->is_unsigned = false;
467 pParam->error = nullptr;
468}
469
470void CMysqlConnection::BindNull(int Idx)
471{
472 m_NewQuery = true;
473 Idx -= 1;
474 dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "Error in BindNull: index out of bounds: %d", Idx);
475
476 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
477 pParam->buffer_type = MYSQL_TYPE_NULL;
478 pParam->buffer = nullptr;
479 pParam->buffer_length = 0;
480 pParam->length = nullptr;
481 pParam->is_null = nullptr;
482 pParam->is_unsigned = false;
483 pParam->error = nullptr;
484}
485
486bool CMysqlConnection::Step(bool *pEnd, char *pError, int ErrorSize)
487{
488 if(m_NewQuery)
489 {
490 m_NewQuery = false;
491 if(mysql_stmt_bind_param(stmt: m_pStmt.get(), bnd: m_vStmtParameters.data()))
492 {
493 StoreErrorStmt(pContext: "bind_param");
494 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
495 return false;
496 }
497 if(mysql_stmt_execute(stmt: m_pStmt.get()))
498 {
499 StoreErrorStmt(pContext: "execute");
500 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
501 return false;
502 }
503 }
504 int Result = mysql_stmt_fetch(stmt: m_pStmt.get());
505 if(Result == 1)
506 {
507 StoreErrorStmt(pContext: "fetch");
508 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
509 return false;
510 }
511 *pEnd = (Result == MYSQL_NO_DATA);
512 // `Result` is now either `MYSQL_DATA_TRUNCATED` (which we ignore, we
513 // fetch our columns in a different way) or `0` aka success.
514 return true;
515}
516
517bool CMysqlConnection::ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize)
518{
519 if(m_NewQuery)
520 {
521 m_NewQuery = false;
522 if(mysql_stmt_bind_param(stmt: m_pStmt.get(), bnd: m_vStmtParameters.data()))
523 {
524 StoreErrorStmt(pContext: "bind_param");
525 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
526 return false;
527 }
528 if(mysql_stmt_execute(stmt: m_pStmt.get()))
529 {
530 StoreErrorStmt(pContext: "execute");
531 str_copy(dst: pError, src: m_aErrorDetail, dst_size: ErrorSize);
532 return false;
533 }
534 *pNumUpdated = mysql_stmt_affected_rows(stmt: m_pStmt.get());
535 return true;
536 }
537 str_copy(dst: pError, src: "tried to execute update without query", dst_size: ErrorSize);
538 return false;
539}
540
541bool CMysqlConnection::IsNull(int Col)
542{
543 Col -= 1;
544
545 MYSQL_BIND Bind;
546 my_bool IsNull;
547 mem_zero(block: &Bind, size: sizeof(Bind));
548 Bind.buffer_type = MYSQL_TYPE_NULL;
549 Bind.buffer = nullptr;
550 Bind.buffer_length = 0;
551 Bind.length = nullptr;
552 Bind.is_null = &IsNull;
553 Bind.is_unsigned = false;
554 Bind.error = nullptr;
555 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
556 {
557 StoreErrorStmt(pContext: "fetch_column:null");
558 dbg_assert_failed("Error in IsNull(%d): error fetching column %s", Col + 1, m_aErrorDetail);
559 }
560 return IsNull;
561}
562
563float CMysqlConnection::GetFloat(int Col)
564{
565 Col -= 1;
566
567 MYSQL_BIND Bind;
568 float Value;
569 my_bool IsNull;
570 mem_zero(block: &Bind, size: sizeof(Bind));
571 Bind.buffer_type = MYSQL_TYPE_FLOAT;
572 Bind.buffer = &Value;
573 Bind.buffer_length = sizeof(Value);
574 Bind.length = nullptr;
575 Bind.is_null = &IsNull;
576 Bind.is_unsigned = false;
577 Bind.error = nullptr;
578 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
579 {
580 StoreErrorStmt(pContext: "fetch_column:float");
581 dbg_assert_failed("Error in GetFloat(%d): error fetching column %s", Col + 1, m_aErrorDetail);
582 }
583 dbg_assert(!IsNull, "Error in GetFloat(%d): NULL", Col + 1);
584 return Value;
585}
586
587int CMysqlConnection::GetInt(int Col)
588{
589 Col -= 1;
590
591 MYSQL_BIND Bind;
592 int Value;
593 my_bool IsNull;
594 mem_zero(block: &Bind, size: sizeof(Bind));
595 Bind.buffer_type = MYSQL_TYPE_LONG;
596 Bind.buffer = &Value;
597 Bind.buffer_length = sizeof(Value);
598 Bind.length = nullptr;
599 Bind.is_null = &IsNull;
600 Bind.is_unsigned = false;
601 Bind.error = nullptr;
602 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
603 {
604 StoreErrorStmt(pContext: "fetch_column:int");
605 dbg_assert_failed("Error in GetInt(%d): error fetching column %s", Col + 1, m_aErrorDetail);
606 }
607 dbg_assert(!IsNull, "Error in GetInt(%d): NULL", Col + 1);
608 return Value;
609}
610
611int64_t CMysqlConnection::GetInt64(int Col)
612{
613 Col -= 1;
614
615 MYSQL_BIND Bind;
616 int64_t Value;
617 my_bool IsNull;
618 mem_zero(block: &Bind, size: sizeof(Bind));
619 Bind.buffer_type = MYSQL_TYPE_LONGLONG;
620 Bind.buffer = &Value;
621 Bind.buffer_length = sizeof(Value);
622 Bind.length = nullptr;
623 Bind.is_null = &IsNull;
624 Bind.is_unsigned = false;
625 Bind.error = nullptr;
626 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
627 {
628 StoreErrorStmt(pContext: "fetch_column:int64");
629 dbg_assert_failed("Error in GetInt64(%d): error fetching column %s", Col + 1, m_aErrorDetail);
630 }
631 dbg_assert(!IsNull, "Error in GetInt64(%d): NULL", Col + 1);
632 return Value;
633}
634
635void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize)
636{
637 Col -= 1;
638
639 for(int i = 0; i < BufferSize; i++)
640 {
641 pBuffer[i] = '\0';
642 }
643
644 MYSQL_BIND Bind;
645 unsigned long Length;
646 my_bool IsNull;
647 my_bool Error;
648 mem_zero(block: &Bind, size: sizeof(Bind));
649 Bind.buffer_type = MYSQL_TYPE_STRING;
650 Bind.buffer = pBuffer;
651 // leave one character for null-termination
652 Bind.buffer_length = BufferSize - 1;
653 Bind.length = &Length;
654 Bind.is_null = &IsNull;
655 Bind.is_unsigned = false;
656 Bind.error = &Error;
657 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
658 {
659 StoreErrorStmt(pContext: "fetch_column:string");
660 dbg_assert_failed("Error in GetString(%d): error fetching column %s", Col + 1, m_aErrorDetail);
661 }
662 dbg_assert(!IsNull, "Error in GetString(%d): NULL", Col + 1);
663 dbg_assert(!Error, "Error in GetString(%d): truncation occurred", Col + 1);
664}
665
666int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
667{
668 Col -= 1;
669
670 MYSQL_BIND Bind;
671 unsigned long Length;
672 my_bool IsNull;
673 my_bool Error;
674 mem_zero(block: &Bind, size: sizeof(Bind));
675 Bind.buffer_type = MYSQL_TYPE_BLOB;
676 Bind.buffer = pBuffer;
677 Bind.buffer_length = BufferSize;
678 Bind.length = &Length;
679 Bind.is_null = &IsNull;
680 Bind.is_unsigned = false;
681 Bind.error = &Error;
682 if(mysql_stmt_fetch_column(stmt: m_pStmt.get(), bind_arg: &Bind, column: Col, offset: 0))
683 {
684 StoreErrorStmt(pContext: "fetch_column:blob");
685 dbg_assert_failed("Error in GetBlob(%d): error fetching column %s", Col + 1, m_aErrorDetail);
686 }
687 dbg_assert(!IsNull, "Error in GetBlob(%d): NULL", Col + 1);
688 dbg_assert(!Error, "Error in GetBlob(%d): truncation occurred", Col + 1);
689 return Length;
690}
691
692bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize)
693{
694 char aBuf[512];
695 str_format(buffer: aBuf, buffer_size: sizeof(aBuf),
696 format: "INSERT INTO %s_points(Name, Points) "
697 "VALUES (?, ?) "
698 "ON DUPLICATE KEY UPDATE Points=Points+?",
699 GetPrefix());
700 if(!PrepareStatement(pStmt: aBuf, pError, ErrorSize))
701 {
702 return false;
703 }
704 BindString(Idx: 1, pString: pPlayer);
705 BindInt(Idx: 2, Value: Points);
706 BindInt(Idx: 3, Value: Points);
707 int NumUpdated;
708 return ExecuteUpdate(pNumUpdated: &NumUpdated, pError, ErrorSize);
709}
710
711std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config)
712{
713 return std::make_unique<CMysqlConnection>(args&: Config);
714}
715#else
716bool MysqlAvailable()
717{
718 return false;
719}
720int MysqlInit()
721{
722 return 0;
723}
724void MysqlUninit()
725{
726}
727std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config)
728{
729 return nullptr;
730}
731#endif
732