ddnet/src/engine/server/databases/mysql.cpp

739 lines
19 KiB
C++
Raw Normal View History

#include "connection.h"
2021-11-28 00:31:22 +00:00
#if defined(CONF_MYSQL)
#include <mysql.h>
2020-07-04 08:13:21 +00:00
2020-08-09 15:54:25 +00:00
#include <base/tl/threading.h>
2020-08-03 14:18:22 +00:00
#include <engine/console.h>
#include <atomic>
#include <memory>
#include <vector>
// MySQL >= 8.0.1 removed my_bool, 8.0.2 accidentally reintroduced it: https://bugs.mysql.com/bug.php?id=87337
#if !defined(LIBMARIADB) && MYSQL_VERSION_ID >= 80001 && MYSQL_VERSION_ID != 80002
typedef bool my_bool;
#endif
enum
{
MYSQLSTATE_UNINITIALIZED,
MYSQLSTATE_INITIALIZED,
MYSQLSTATE_SHUTTINGDOWN,
};
std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED};
std::atomic_int g_MysqlNumConnections;
int MysqlInit()
{
dbg_assert(mysql_thread_safe(), "MySQL library without thread safety");
dbg_assert(g_MysqlState == MYSQLSTATE_UNINITIALIZED, "double MySQL initialization");
if(mysql_library_init(0, nullptr, nullptr))
{
return 1;
}
int Uninitialized = MYSQLSTATE_UNINITIALIZED;
bool Swapped = g_MysqlState.compare_exchange_strong(Uninitialized, MYSQLSTATE_INITIALIZED);
(void)Swapped;
dbg_assert(Swapped, "MySQL double initialization");
return 0;
}
void MysqlUninit()
{
int Initialized = MYSQLSTATE_INITIALIZED;
bool Swapped = g_MysqlState.compare_exchange_strong(Initialized, MYSQLSTATE_SHUTTINGDOWN);
(void)Swapped;
dbg_assert(Swapped, "double MySQL free or free without initialization");
int Counter = g_MysqlNumConnections;
if(Counter != 0)
{
dbg_msg("mysql", "can't deinitialize, connections remaining: %d", Counter);
return;
}
mysql_library_end();
}
class CMysqlConnection : public IDbConnection
{
public:
CMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup);
~CMysqlConnection();
void Print(IConsole *pConsole, const char *pMode) override;
CMysqlConnection *Copy() override;
const char *BinaryCollate() const override { return "utf8mb4_bin"; }
void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) override;
const char *InsertTimestampAsUtc() const override { return "?"; }
const char *CollateNocase() const override { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
const char *InsertIgnore() const override { return "INSERT IGNORE"; }
const char *Random() const override { return "RAND()"; }
const char *MedianMapTime(char *pBuffer, int BufferSize) const override;
const char *False() const override { return "FALSE"; }
const char *True() const override { return "TRUE"; }
bool Connect(char *pError, int ErrorSize) override;
void Disconnect() override;
bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize) override;
void BindString(int Idx, const char *pString) override;
void BindBlob(int Idx, unsigned char *pBlob, int Size) override;
void BindInt(int Idx, int Value) override;
void BindInt64(int Idx, int64_t Value) override;
void BindFloat(int Idx, float Value) override;
void Print() override {}
bool Step(bool *pEnd, char *pError, int ErrorSize) override;
bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) override;
bool IsNull(int Col) override;
float GetFloat(int Col) override;
int GetInt(int Col) override;
int64_t GetInt64(int Col) override;
void GetString(int Col, char *pBuffer, int BufferSize) override;
int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) override;
bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
private:
class CStmtDeleter
{
public:
void operator()(MYSQL_STMT *pStmt) const;
};
char m_aErrorDetail[128];
void StoreErrorMysql(const char *pContext);
void StoreErrorStmt(const char *pContext);
bool ConnectImpl();
bool PrepareAndExecuteStatement(const char *pStmt);
//static void DeleteResult(MYSQL_RES *pResult);
union UParameterExtra
{
int i;
unsigned long ul;
float f;
};
bool m_NewQuery = false;
bool m_HaveConnection = false;
MYSQL m_Mysql;
std::unique_ptr<MYSQL_STMT, CStmtDeleter> m_pStmt = nullptr;
std::vector<MYSQL_BIND> m_vStmtParameters;
std::vector<UParameterExtra> m_vStmtParameterExtras;
// copy of config vars
char m_aDatabase[64];
char m_aUser[64];
char m_aPass[64];
char m_aIp[64];
int m_Port;
bool m_Setup;
std::atomic_bool m_InUse;
};
void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const
{
mysql_stmt_close(pStmt);
}
2020-07-04 08:13:21 +00:00
CMysqlConnection::CMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup) :
2020-07-04 08:13:21 +00:00
IDbConnection(pPrefix),
m_Port(Port),
m_Setup(Setup),
m_InUse(false)
{
g_MysqlNumConnections += 1;
dbg_assert(g_MysqlState == MYSQLSTATE_INITIALIZED, "MySQL library not in initialized state");
mem_zero(m_aErrorDetail, sizeof(m_aErrorDetail));
mem_zero(&m_Mysql, sizeof(m_Mysql));
mysql_init(&m_Mysql);
2020-07-04 08:13:21 +00:00
str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase));
str_copy(m_aUser, pUser, sizeof(m_aUser));
str_copy(m_aPass, pPass, sizeof(m_aPass));
str_copy(m_aIp, pIp, sizeof(m_aIp));
}
CMysqlConnection::~CMysqlConnection()
{
mysql_close(&m_Mysql);
g_MysqlNumConnections -= 1;
}
void CMysqlConnection::StoreErrorMysql(const char *pContext)
{
str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:mysql:%d): %s", pContext, mysql_errno(&m_Mysql), mysql_error(&m_Mysql));
}
void CMysqlConnection::StoreErrorStmt(const char *pContext)
{
str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:stmt:%d): %s", pContext, mysql_stmt_errno(m_pStmt.get()), mysql_stmt_error(m_pStmt.get()));
}
bool CMysqlConnection::PrepareAndExecuteStatement(const char *pStmt)
{
if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt)))
{
StoreErrorStmt("prepare");
return true;
}
if(mysql_stmt_execute(m_pStmt.get()))
{
StoreErrorStmt("execute");
return true;
}
return false;
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::Print(IConsole *pConsole, const char *pMode)
2020-08-03 14:18:22 +00:00
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
pMode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port);
2020-08-03 14:18:22 +00:00
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
2020-07-04 08:13:21 +00:00
CMysqlConnection *CMysqlConnection::Copy()
{
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup);
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp);
}
bool CMysqlConnection::Connect(char *pError, int ErrorSize)
2020-07-04 08:13:21 +00:00
{
if(m_InUse.exchange(true))
{
dbg_assert(0, "Tried connecting while the connection is in use");
}
2020-07-04 08:13:21 +00:00
m_NewQuery = true;
if(ConnectImpl())
{
str_copy(pError, m_aErrorDetail, ErrorSize);
m_InUse.store(false);
return true;
}
return false;
}
bool CMysqlConnection::ConnectImpl()
{
if(m_HaveConnection)
{
if(m_pStmt && mysql_stmt_free_result(m_pStmt.get()))
{
StoreErrorStmt("free_result");
dbg_msg("mysql", "can't free last result %s", m_aErrorDetail);
}
if(!mysql_select_db(&m_Mysql, m_aDatabase))
{
// Success.
return false;
}
StoreErrorMysql("select_db");
dbg_msg("mysql", "ping error, trying to reconnect %s", m_aErrorDetail);
mysql_close(&m_Mysql);
mem_zero(&m_Mysql, sizeof(m_Mysql));
mysql_init(&m_Mysql);
}
m_pStmt = nullptr;
unsigned int OptConnectTimeout = 60;
unsigned int OptReadTimeout = 60;
unsigned int OptWriteTimeout = 120;
my_bool OptReconnect = true;
mysql_options(&m_Mysql, MYSQL_OPT_CONNECT_TIMEOUT, &OptConnectTimeout);
mysql_options(&m_Mysql, MYSQL_OPT_READ_TIMEOUT, &OptReadTimeout);
mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout);
mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect);
mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
if(!mysql_real_connect(&m_Mysql, m_aIp, m_aUser, m_aPass, nullptr, m_Port, nullptr, CLIENT_IGNORE_SIGPIPE))
{
StoreErrorMysql("real_connect");
return true;
}
m_HaveConnection = true;
m_pStmt = std::unique_ptr<MYSQL_STMT, CStmtDeleter>(mysql_stmt_init(&m_Mysql));
// Apparently MYSQL_SET_CHARSET_NAME is not enough
if(PrepareAndExecuteStatement("SET CHARACTER SET utf8mb4"))
{
return true;
}
if(m_Setup)
{
char aCreateDatabase[1024];
// create database
str_format(aCreateDatabase, sizeof(aCreateDatabase), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
if(PrepareAndExecuteStatement(aCreateDatabase))
{
return true;
}
}
// Connect to specific database
if(mysql_select_db(&m_Mysql, m_aDatabase))
{
StoreErrorMysql("select_db");
return true;
}
if(m_Setup)
{
char aCreateRace[1024];
char aCreateTeamrace[1024];
char aCreateMaps[1024];
char aCreateSaves[1024];
char aCreatePoints[1024];
FormatCreateRace(aCreateRace, sizeof(aCreateRace));
FormatCreateTeamrace(aCreateTeamrace, sizeof(aCreateTeamrace), "VARBINARY(16)");
FormatCreateMaps(aCreateMaps, sizeof(aCreateMaps));
FormatCreateSaves(aCreateSaves, sizeof(aCreateSaves));
FormatCreatePoints(aCreatePoints, sizeof(aCreatePoints));
if(PrepareAndExecuteStatement(aCreateRace) ||
PrepareAndExecuteStatement(aCreateTeamrace) ||
PrepareAndExecuteStatement(aCreateMaps) ||
PrepareAndExecuteStatement(aCreateSaves) ||
PrepareAndExecuteStatement(aCreatePoints))
{
return true;
}
m_Setup = false;
}
dbg_msg("mysql", "connection established");
return false;
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::Disconnect()
{
m_InUse.store(false);
}
bool CMysqlConnection::PrepareStatement(const char *pStmt, char *pError, int ErrorSize)
2020-07-04 08:13:21 +00:00
{
if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt)))
{
StoreErrorStmt("prepare");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
m_NewQuery = true;
unsigned NumParameters = mysql_stmt_param_count(m_pStmt.get());
m_vStmtParameters.resize(NumParameters);
m_vStmtParameterExtras.resize(NumParameters);
mem_zero(&m_vStmtParameters[0], sizeof(m_vStmtParameters[0]) * m_vStmtParameters.size());
mem_zero(&m_vStmtParameterExtras[0], sizeof(m_vStmtParameterExtras[0]) * m_vStmtParameterExtras.size());
return false;
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::BindString(int Idx, const char *pString)
{
m_NewQuery = true;
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
int Length = str_length(pString);
m_vStmtParameterExtras[Idx].ul = Length;
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_STRING;
pParam->buffer = (void *)pString;
pParam->buffer_length = Length + 1;
pParam->length = &m_vStmtParameterExtras[Idx].ul;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
{
m_NewQuery = true;
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
m_vStmtParameterExtras[Idx].ul = Size;
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_BLOB;
pParam->buffer = pBlob;
pParam->buffer_length = Size;
pParam->length = &m_vStmtParameterExtras[Idx].ul;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
2020-07-04 08:13:21 +00:00
void CMysqlConnection::BindInt(int Idx, int Value)
{
m_NewQuery = true;
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
m_vStmtParameterExtras[Idx].i = Value;
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_LONG;
pParam->buffer = &m_vStmtParameterExtras[Idx].i;
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
pParam->length = nullptr;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
2021-11-19 11:29:24 +00:00
pParam->error = nullptr;
}
void CMysqlConnection::BindInt64(int Idx, int64_t Value)
{
m_NewQuery = true;
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
2021-11-19 11:29:24 +00:00
m_vStmtParameterExtras[Idx].i = Value;
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
2021-11-19 11:29:24 +00:00
pParam->buffer_type = MYSQL_TYPE_LONGLONG;
pParam->buffer = &m_vStmtParameterExtras[Idx].i;
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
2021-11-19 11:29:24 +00:00
pParam->length = nullptr;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
2020-07-04 08:13:21 +00:00
}
void CMysqlConnection::BindFloat(int Idx, float Value)
{
m_NewQuery = true;
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
m_vStmtParameterExtras[Idx].f = Value;
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_FLOAT;
pParam->buffer = &m_vStmtParameterExtras[Idx].f;
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
pParam->length = nullptr;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
bool CMysqlConnection::Step(bool *pEnd, char *pError, int ErrorSize)
2020-07-04 08:13:21 +00:00
{
if(m_NewQuery)
{
m_NewQuery = false;
if(mysql_stmt_bind_param(m_pStmt.get(), &m_vStmtParameters[0]))
{
StoreErrorStmt("bind_param");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
if(mysql_stmt_execute(m_pStmt.get()))
{
StoreErrorStmt("execute");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
}
int Result = mysql_stmt_fetch(m_pStmt.get());
if(Result == 1)
{
StoreErrorStmt("fetch");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
*pEnd = (Result == MYSQL_NO_DATA);
// `Result` is now either `MYSQL_DATA_TRUNCATED` (which we ignore, we
// fetch our columns in a different way) or `0` aka success.
return false;
2020-07-04 08:13:21 +00:00
}
bool CMysqlConnection::ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize)
{
if(m_NewQuery)
{
m_NewQuery = false;
if(mysql_stmt_bind_param(m_pStmt.get(), &m_vStmtParameters[0]))
{
StoreErrorStmt("bind_param");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
if(mysql_stmt_execute(m_pStmt.get()))
{
StoreErrorStmt("execute");
str_copy(pError, m_aErrorDetail, ErrorSize);
return true;
}
*pNumUpdated = mysql_stmt_affected_rows(m_pStmt.get());
return false;
}
str_copy(pError, "tried to execute update without query", ErrorSize);
return true;
}
bool CMysqlConnection::IsNull(int Col)
2020-07-04 08:13:21 +00:00
{
Col -= 1;
MYSQL_BIND Bind;
my_bool IsNull;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_NULL;
Bind.buffer = nullptr;
Bind.buffer_length = 0;
Bind.length = nullptr;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = nullptr;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:null");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in IsNull");
}
return IsNull;
}
float CMysqlConnection::GetFloat(int Col)
{
Col -= 1;
MYSQL_BIND Bind;
float Value;
my_bool IsNull;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_FLOAT;
Bind.buffer = &Value;
Bind.buffer_length = sizeof(Value);
Bind.length = nullptr;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = nullptr;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:float");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetFloat");
}
if(IsNull)
{
dbg_assert(0, "error getting float: NULL");
}
return Value;
2020-07-04 08:13:21 +00:00
}
int CMysqlConnection::GetInt(int Col)
2020-07-04 08:13:21 +00:00
{
Col -= 1;
MYSQL_BIND Bind;
int Value;
my_bool IsNull;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_LONG;
Bind.buffer = &Value;
Bind.buffer_length = sizeof(Value);
Bind.length = nullptr;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = nullptr;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:int");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetInt");
}
if(IsNull)
{
dbg_assert(0, "error getting int: NULL");
}
return Value;
2020-07-04 08:13:21 +00:00
}
2021-11-07 15:48:57 +00:00
int64_t CMysqlConnection::GetInt64(int Col)
{
Col -= 1;
MYSQL_BIND Bind;
int64_t Value;
my_bool IsNull;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_LONGLONG;
Bind.buffer = &Value;
Bind.buffer_length = sizeof(Value);
Bind.length = nullptr;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = nullptr;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:int64");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetInt64");
}
if(IsNull)
{
dbg_assert(0, "error getting int: NULL");
}
return Value;
}
void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize)
2020-07-04 08:13:21 +00:00
{
Col -= 1;
for(int i = 0; i < BufferSize; i++)
{
pBuffer[i] = 'a';
}
MYSQL_BIND Bind;
unsigned long Length;
my_bool IsNull;
my_bool Error;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_STRING;
Bind.buffer = pBuffer;
Bind.buffer_length = BufferSize;
Bind.length = &Length;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = &Error;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:string");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetString");
}
if(IsNull)
{
dbg_assert(0, "error getting string: NULL");
}
if(Error)
{
dbg_assert(0, "error getting string: truncation occured");
}
2020-07-04 08:13:21 +00:00
}
int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
2020-07-04 08:13:21 +00:00
{
Col -= 1;
MYSQL_BIND Bind;
unsigned long Length;
my_bool IsNull;
my_bool Error;
mem_zero(&Bind, sizeof(Bind));
Bind.buffer_type = MYSQL_TYPE_BLOB;
Bind.buffer = pBuffer;
Bind.buffer_length = BufferSize;
Bind.length = &Length;
Bind.is_null = &IsNull;
Bind.is_unsigned = false;
Bind.error = &Error;
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
{
StoreErrorStmt("fetch_column:blob");
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
dbg_assert(0, "error in GetBlob");
}
if(IsNull)
{
dbg_assert(0, "error getting blob: NULL");
}
if(Error)
{
dbg_assert(0, "error getting blob: truncation occured");
}
return Length;
2020-07-04 08:13:21 +00:00
}
const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const
{
str_format(pBuffer, BufferSize,
"SELECT MEDIAN(Time) "
"OVER (PARTITION BY Map) "
"FROM %s_race "
"WHERE Map = l.Map "
"LIMIT 1",
GetPrefix());
return pBuffer;
}
bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_points(Name, Points) "
"VALUES (?, ?) "
"ON DUPLICATE KEY UPDATE Points=Points+?",
GetPrefix());
if(PrepareStatement(aBuf, pError, ErrorSize))
{
return true;
}
BindString(1, pPlayer);
BindInt(2, Points);
BindInt(3, Points);
int NumUpdated;
return ExecuteUpdate(&NumUpdated, pError, ErrorSize);
}
std::unique_ptr<IDbConnection> CreateMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup)
{
return std::make_unique<CMysqlConnection>(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup);
}
#else
int MysqlInit()
{
return 0;
}
void MysqlUninit()
{
}
std::unique_ptr<IDbConnection> CreateMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup)
{
return nullptr;
}
#endif