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 *MedianMapTime(char *pBuffer, int BufferSize) const override;
82 const char *False() const override { return "FALSE"; }
83 const char *True() const override { return "TRUE"; }
84
85 bool Connect(char *pError, int ErrorSize) override;
86 void Disconnect() override;
87
88 bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize) override;
89
90 void BindString(int Idx, const char *pString) override;
91 void BindBlob(int Idx, unsigned char *pBlob, int Size) override;
92 void BindInt(int Idx, int Value) override;
93 void BindInt64(int Idx, int64_t Value) override;
94 void BindFloat(int Idx, float Value) override;
95 void BindNull(int Idx) override;
96
97 void Print() override {}
98 bool Step(bool *pEnd, char *pError, int ErrorSize) override;
99 bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) override;
100
101 bool IsNull(int Col) override;
102 float GetFloat(int Col) override;
103 int GetInt(int Col) override;
104 int64_t GetInt64(int Col) override;
105 void GetString(int Col, char *pBuffer, int BufferSize) override;
106 int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) override;
107
108 bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
109
110private:
111 class CStmtDeleter
112 {
113 public:
114 void operator()(MYSQL_STMT *pStmt) const;
115 };
116
117 char m_aErrorDetail[128];
118 void StoreErrorMysql(const char *pContext);
119 void StoreErrorStmt(const char *pContext);
120 bool ConnectImpl();
121 bool PrepareAndExecuteStatement(const char *pStmt);
122
123 union UParameterExtra
124 {
125 int m_Int;
126 int64_t m_Int64;
127 unsigned long m_UnsignedLong;
128 float m_Float;
129 };
130
131 bool m_NewQuery = false;
132 bool m_HaveConnection = false;
133 MYSQL m_Mysql;
134 std::unique_ptr<MYSQL_STMT, CStmtDeleter> m_pStmt = nullptr;
135 std::vector<MYSQL_BIND> m_vStmtParameters;
136 std::vector<UParameterExtra> m_vStmtParameterExtras;
137
138 // copy of m_Config vars
139 CMysqlConfig m_Config;
140
141 std::atomic_bool m_InUse;
142};
143
144void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const
145{
146 mysql_stmt_close(stmt: pStmt);
147}
148
149CMysqlConnection::CMysqlConnection(CMysqlConfig Config) :
150 IDbConnection(Config.m_aPrefix),
151 m_Config(Config),
152 m_InUse(false)
153{
154 g_MysqlNumConnections += 1;
155 dbg_assert(g_MysqlState == MYSQLSTATE_INITIALIZED, "MySQL library not in initialized state");
156
157 m_aErrorDetail[0] = '\0';
158 mem_zero(block: &m_Mysql, size: sizeof(m_Mysql));
159 mysql_init(mysql: &m_Mysql);
160}
161
162CMysqlConnection::~CMysqlConnection()
163{
164 mysql_close(sock: &m_Mysql);
165 g_MysqlNumConnections -= 1;
166}
167
168void CMysqlConnection::StoreErrorMysql(const char *pContext)
169{
170 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));
171}
172
173void CMysqlConnection::StoreErrorStmt(const char *pContext)
174{
175 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()));
176}
177
178bool CMysqlConnection::PrepareAndExecuteStatement(const char *pStmt)
179{
180 if(mysql_stmt_prepare(stmt: m_pStmt.get(), query: pStmt, length: str_length(str: pStmt)))
181 {
182 StoreErrorStmt(pContext: "prepare");
183 return false;
184 }
185 if(mysql_stmt_execute(stmt: m_pStmt.get()))
186 {
187 StoreErrorStmt(pContext: "execute");
188 return false;
189 }
190 return true;
191}
192
193void CMysqlConnection::Print(const char *pMode)
194{
195 log_info("server",
196 "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
197 pMode, m_Config.m_aDatabase, GetPrefix(), m_Config.m_aUser, m_Config.m_aIp, m_Config.m_Port);
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_field_count(stmt: m_pStmt.get()) > 0 && 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(sz: NumParameters);
331 m_vStmtParameterExtras.resize(sz: 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].m_UnsignedLong = 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].m_UnsignedLong;
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].m_UnsignedLong = 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].m_UnsignedLong;
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].m_Int = Value;
382 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
383 pParam->buffer_type = MYSQL_TYPE_LONG;
384 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Int;
385 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Int);
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].m_Int64 = Value;
399 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
400 pParam->buffer_type = MYSQL_TYPE_LONGLONG;
401 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Int64;
402 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Int64);
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].m_Float = Value;
416 MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
417 pParam->buffer_type = MYSQL_TYPE_FLOAT;
418 pParam->buffer = &m_vStmtParameterExtras[Idx].m_Float;
419 pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].m_Float);
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(%d): error fetching column %s", Col + 1, 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(%d): error fetching column %s", Col + 1, m_aErrorDetail);
538 }
539 dbg_assert(!IsNull, "Error in GetFloat(%d): NULL", Col + 1);
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(%d): error fetching column %s", Col + 1, m_aErrorDetail);
562 }
563 dbg_assert(!IsNull, "Error in GetInt(%d): NULL", Col + 1);
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(%d): error fetching column %s", Col + 1, m_aErrorDetail);
586 }
587 dbg_assert(!IsNull, "Error in GetInt64(%d): NULL", Col + 1);
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(%d): error fetching column %s", Col + 1, m_aErrorDetail);
617 }
618 dbg_assert(!IsNull, "Error in GetString(%d): NULL", Col + 1);
619 dbg_assert(!Error, "Error in GetString(%d): truncation occurred", Col + 1);
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(%d): error fetching column %s", Col + 1, m_aErrorDetail);
642 }
643 dbg_assert(!IsNull, "Error in GetBlob(%d): NULL", Col + 1);
644 dbg_assert(!Error, "Error in GetBlob(%d): truncation occurred", Col + 1);
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