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