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 |
17 | typedef bool my_bool; |
18 | #endif |
19 | |
20 | enum |
21 | { |
22 | MYSQLSTATE_UNINITIALIZED, |
23 | MYSQLSTATE_INITIALIZED, |
24 | MYSQLSTATE_SHUTTINGDOWN, |
25 | }; |
26 | |
27 | std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED}; |
28 | std::atomic_int g_MysqlNumConnections; |
29 | |
30 | bool MysqlAvailable() |
31 | { |
32 | return true; |
33 | } |
34 | |
35 | int 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 | |
50 | void 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 | |
65 | class CMysqlConnection : public IDbConnection |
66 | { |
67 | public: |
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 | |
106 | private: |
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 |
121 | { |
122 | int ; |
123 | unsigned long ; |
124 | float ; |
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> ; |
133 | |
134 | // copy of m_Config vars |
135 | CMysqlConfig m_Config; |
136 | |
137 | std::atomic_bool m_InUse; |
138 | }; |
139 | |
140 | void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const |
141 | { |
142 | mysql_stmt_close(stmt: pStmt); |
143 | } |
144 | |
145 | CMysqlConnection::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 | |
158 | CMysqlConnection::~CMysqlConnection() |
159 | { |
160 | mysql_close(sock: &m_Mysql); |
161 | g_MysqlNumConnections -= 1; |
162 | } |
163 | |
164 | void 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 | |
169 | void 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 | |
174 | bool 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 | |
189 | void 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 | |
198 | void 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 | |
203 | bool 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 | |
220 | bool 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 | |
316 | void CMysqlConnection::Disconnect() |
317 | { |
318 | m_InUse.store(i: false); |
319 | } |
320 | |
321 | bool 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 | |
338 | void 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 | |
356 | void 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 | |
373 | void 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 | |
390 | void 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 | |
407 | void 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 | |
424 | bool 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 | |
455 | bool 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 | |
479 | bool 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 | |
502 | float 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 | |
530 | int 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 | |
558 | int64_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 | |
586 | void 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 | |
624 | int 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 | |
657 | const 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 | |
669 | bool 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 | |
688 | std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config) |
689 | { |
690 | return std::make_unique<CMysqlConnection>(args&: Config); |
691 | } |
692 | #else |
693 | bool MysqlAvailable() |
694 | { |
695 | return false; |
696 | } |
697 | int MysqlInit() |
698 | { |
699 | return 0; |
700 | } |
701 | void MysqlUninit() |
702 | { |
703 | } |
704 | std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config) |
705 | { |
706 | return nullptr; |
707 | } |
708 | #endif |
709 | |