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