Get rid of the MySQL C++ connector as a dependency

We now use the C API directly. This has the advantage of using one
obscure dependency less, as the C++ connector also used the C API.
This commit is contained in:
heinrich5991 2021-01-26 21:22:32 +01:00
parent a1fb94c6b7
commit d7019a244e
10 changed files with 647 additions and 375 deletions

View file

@ -63,7 +63,7 @@ jobs:
- name: Prepare Linux (fancy)
if: contains(matrix.os, 'ubuntu') && matrix.fancy
run: |
sudo apt-get install libboost-dev libmariadbclient-dev libmysqlcppconn-dev libwebsockets-dev -y
sudo apt-get install libmariadbclient-dev libwebsockets-dev -y
- name: Prepare MacOS
if: contains(matrix.os, 'macOS')

View file

@ -1964,9 +1964,7 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server
databases/connection_pool.cpp
databases/connection_pool.h
databases/mysql.cpp
databases/mysql.h
databases/sqlite.cpp
databases/sqlite.h
name_ban.cpp
name_ban.h
register.cpp

View file

@ -39,30 +39,13 @@ endif()
set_extra_dirs_lib(MYSQL mysql)
find_library(MYSQL_LIBRARY
NAMES "mysqlclient" "mysqlclient_r" "mariadbclient"
HINTS ${HINTS_MYSQL_LIBDIR} ${MYSQL_CONFIG_LIBRARY_PATH}
PATHS ${PATHS_MYSQL_LIBDIR}
HINTS ${MYSQL_CONFIG_LIBRARY_PATH}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
set_extra_dirs_include(MYSQL mysql "${MYSQL_LIBRARY}")
find_path(MYSQL_INCLUDEDIR
NAMES "mysql.h"
HINTS ${HINTS_MYSQL_INCLUDEDIR} ${MYSQL_CONFIG_INCLUDE_DIR}
PATHS ${PATHS_MYSQL_INCLUDEDIR}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
set_extra_dirs_lib(MYSQL_CPPCONN mysql)
find_library(MYSQL_CPPCONN_LIBRARY
NAMES "mysqlcppconn" "mysqlcppconn-static"
HINTS ${HINTS_MYSQL_CPPCONN_LIBDIR} ${MYSQL_CONFIG_LIBRARY_PATH}
PATHS ${PATHS_MYSQL_CPPCONN_LIBDIR}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
set_extra_dirs_include(MYSQL_CPPCONN mysql "${MYSQL_CPPCONN_LIBRARY}")
find_path(MYSQL_CPPCONN_INCLUDEDIR
NAMES "mysql_connection.h"
HINTS ${HINTS_MYSQL_CPPCONN_INCLUDEDIR} ${MYSQL_CONFIG_INCLUDE_DIR}
PATHS ${PATHS_MYSQL_CPPCONN_INCLUDEDIR}
HINTS ${MYSQL_CONFIG_INCLUDE_DIR}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
@ -70,10 +53,8 @@ include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MySQL DEFAULT_MSG MYSQL_LIBRARY MYSQL_INCLUDEDIR)
if(MYSQL_FOUND)
is_bundled(MYSQL_BUNDLED "${MYSQL_LIBRARY}")
set(MYSQL_LIBRARIES ${MYSQL_LIBRARY} ${MYSQL_CPPCONN_LIBRARY})
set(MYSQL_INCLUDE_DIRS ${MYSQL_INCLUDEDIR} ${MYSQL_CPPCONN_INCLUDEDIR})
set(MYSQL_LIBRARIES ${MYSQL_LIBRARY})
set(MYSQL_INCLUDE_DIRS ${MYSQL_INCLUDEDIR})
mark_as_advanced(MYSQL_INCLUDEDIR MYSQL_LIBRARY)
endif()

View file

@ -64,16 +64,15 @@ public:
// when called multiple times the next row is selected
virtual bool Step() = 0;
// executes the query and returns the number of rows affected by the update/insert/delete
// FIXME(2020-01-20): change function to AffectedRows() when moved to c-api of MySQL
virtual int ExecuteUpdate() = 0;
virtual bool IsNull(int Col) const = 0;
virtual float GetFloat(int Col) const = 0;
virtual int GetInt(int Col) const = 0;
virtual bool IsNull(int Col) = 0;
virtual float GetFloat(int Col) = 0;
virtual int GetInt(int Col) = 0;
// ensures that the string is null terminated
virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0;
virtual void GetString(int Col, char *pBuffer, int BufferSize) = 0;
// returns number of bytes read into the buffer
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0;
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) = 0;
// SQL statements, that can't be abstracted, has side effects to the result
virtual void AddPoints(const char *pPlayer, int Points) = 0;
@ -89,4 +88,18 @@ protected:
void FormatCreatePoints(char *aBuf, unsigned int BufferSize);
};
int MysqlInit();
void MysqlUninit();
IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup);
// Returns nullptr if MySQL support is not compiled in.
IDbConnection *CreateMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup);
#endif // ENGINE_SERVER_DATABASES_CONNECTION_H

View file

@ -2,9 +2,6 @@
#include "connection.h"
#include <engine/console.h>
#if defined(CONF_SQL)
#include <cppconn/exception.h>
#endif
#include <stdexcept>
// helper struct to hold thread data
@ -223,12 +220,6 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD
break;
}
}
#if defined(CONF_SQL)
catch(sql::SQLException &e)
{
dbg_msg("sql", "%s MySQL Error: %s", pData->m_pName, e.what());
}
#endif
catch(std::runtime_error &e)
{
dbg_msg("sql", "%s SQLite Error: %s", pData->m_pName, e.what());

View file

@ -1,17 +1,145 @@
#include "mysql.h"
#include "connection.h"
#if defined(CONF_SQL)
#include <mysql.h>
#include <base/tl/threading.h>
#include <engine/console.h>
#if defined(CONF_SQL)
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/statement.h>
#endif
#include <string>
#include <atomic>
#include <memory>
#include <stdexcept>
#include <vector>
CLock CMysqlConnection::m_SqlDriverLock;
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);
virtual ~CMysqlConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CMysqlConnection *Copy();
virtual const char *BinaryCollate() const { return "utf8mb4_bin"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "?"; }
virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
virtual const char *InsertIgnore() const { return "INSERT IGNORE"; };
virtual const char *Random() const { return "RAND()"; };
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
virtual Status Connect();
virtual void Disconnect();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual void Print() {}
virtual bool Step();
virtual int ExecuteUpdate();
virtual bool IsNull(int Col);
virtual float GetFloat(int Col);
virtual int GetInt(int Col);
virtual void GetString(int Col, char *pBuffer, int BufferSize);
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize);
virtual void AddPoints(const char *pPlayer, int Points);
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_aStmtParameters;
std::vector<UParameterExtra> m_aStmtParameterExtras;
// 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);
}
CMysqlConnection::CMysqlConnection(
const char *pDatabase,
@ -22,24 +150,52 @@ CMysqlConnection::CMysqlConnection(
int Port,
bool Setup) :
IDbConnection(pPrefix),
#if defined(CONF_SQL)
m_NewQuery(false),
#endif
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);
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));
#ifndef CONF_SQL
dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time");
#endif
}
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;
}
void CMysqlConnection::Print(IConsole *pConsole, const char *Mode)
@ -63,119 +219,109 @@ void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsig
IDbConnection::Status CMysqlConnection::Connect()
{
#if defined(CONF_SQL)
if(m_InUse.exchange(true))
return Status::IN_USE;
m_NewQuery = true;
if(m_pConnection != nullptr)
if(ConnectImpl())
{
try
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
return Status::SUCCESS;
}
catch(sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch(const std::exception &ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch(const std::string &ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch(...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
dbg_msg("mysql", "connect error %s", m_aErrorDetail);
m_InUse.store(false);
return Status::FAILURE;
}
return Status::SUCCESS;
}
dbg_msg("sql", "FAILURE: SQL connection failed, trying to reconnect");
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);
}
try
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))
{
m_pConnection.reset();
m_pPreparedStmt.reset();
m_pResults.reset();
StoreErrorMysql("real_connect");
return true;
}
m_HaveConnection = true;
sql::ConnectOptionsMap connection_properties;
connection_properties["hostName"] = sql::SQLString(m_aIp);
connection_properties["port"] = m_Port;
connection_properties["userName"] = sql::SQLString(m_aUser);
connection_properties["password"] = sql::SQLString(m_aPass);
connection_properties["OPT_CONNECT_TIMEOUT"] = 60;
connection_properties["OPT_READ_TIMEOUT"] = 60;
connection_properties["OPT_WRITE_TIMEOUT"] = 120;
connection_properties["OPT_RECONNECT"] = true;
connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4");
connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4");
m_pStmt = std::unique_ptr<MYSQL_STMT, CStmtDeleter>(mysql_stmt_init(&m_Mysql));
// Create connection
// 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))
{
CScopeLock GlobalLockScope(&m_SqlDriverLock);
sql::Driver *pDriver = get_driver_instance();
m_pConnection.reset(pDriver->connect(connection_properties));
return true;
}
}
// Create Statement
m_pStmt = std::unique_ptr<sql::Statement>(m_pConnection->createStatement());
// Connect to specific database
if(mysql_select_db(&m_Mysql, m_aDatabase))
{
StoreErrorMysql("select_db");
return true;
}
// Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough
m_pStmt->execute("SET CHARACTER SET utf8mb4;");
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(m_Setup)
if(PrepareAndExecuteStatement(aCreateRace) ||
PrepareAndExecuteStatement(aCreateTeamrace) ||
PrepareAndExecuteStatement(aCreateMaps) ||
PrepareAndExecuteStatement(aCreateSaves) ||
PrepareAndExecuteStatement(aCreatePoints))
{
char aBuf[1024];
// create database
str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
m_pStmt->execute(aBuf);
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
FormatCreateRace(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreateTeamrace(aBuf, sizeof(aBuf), "VARBINARY(16)");
m_pStmt->execute(aBuf);
FormatCreateMaps(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreateSaves(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreatePoints(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
m_Setup = false;
return true;
}
else
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
}
dbg_msg("sql", "sql connection established");
return Status::SUCCESS;
m_Setup = false;
}
catch(sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch(const std::exception &ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch(const std::string &ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch(...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
m_InUse.store(false);
#endif
dbg_msg("sql", "FAILURE: sql connection failed");
return Status::FAILURE;
dbg_msg("mysql", "connection established");
return false;
}
void CMysqlConnection::Disconnect()
@ -185,118 +331,303 @@ void CMysqlConnection::Disconnect()
void CMysqlConnection::PrepareStatement(const char *pStmt)
{
#if defined(CONF_SQL)
m_pPreparedStmt.reset(m_pConnection->prepareStatement(pStmt));
if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt)))
{
StoreErrorStmt("prepare");
dbg_msg("mysql", "error preparing statement %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: return true;
}
m_NewQuery = true;
#endif
unsigned NumParameters = mysql_stmt_param_count(m_pStmt.get());
m_aStmtParameters.resize(NumParameters);
m_aStmtParameterExtras.resize(NumParameters);
mem_zero(&m_aStmtParameters[0], sizeof(m_aStmtParameters[0]) * m_aStmtParameters.size());
mem_zero(&m_aStmtParameterExtras[0], sizeof(m_aStmtParameterExtras[0]) * m_aStmtParameterExtras.size());
// TODO: return false;
}
void CMysqlConnection::BindString(int Idx, const char *pString)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setString(Idx, pString);
m_NewQuery = true;
#endif
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds");
int Length = str_length(pString);
m_aStmtParameterExtras[Idx].ul = Length;
MYSQL_BIND *pParam = &m_aStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_STRING;
pParam->buffer = (void *)pString;
pParam->buffer_length = Length + 1;
pParam->length = &m_aStmtParameterExtras[Idx].ul;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
{
#if defined(CONF_SQL)
// copy blob into string
auto Blob = std::string(pBlob, pBlob + Size);
m_pPreparedStmt->setString(Idx, Blob);
m_NewQuery = true;
#endif
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds");
m_aStmtParameterExtras[Idx].ul = Size;
MYSQL_BIND *pParam = &m_aStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_BLOB;
pParam->buffer = pBlob;
pParam->buffer_length = Size;
pParam->length = &m_aStmtParameterExtras[Idx].ul;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
void CMysqlConnection::BindInt(int Idx, int Value)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setInt(Idx, Value);
m_NewQuery = true;
#endif
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds");
m_aStmtParameterExtras[Idx].i = Value;
MYSQL_BIND *pParam = &m_aStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_LONG;
pParam->buffer = &m_aStmtParameterExtras[Idx].i;
pParam->buffer_length = sizeof(m_aStmtParameterExtras[Idx].i);
pParam->length = nullptr;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
void CMysqlConnection::BindFloat(int Idx, float Value)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setDouble(Idx, (double)Value);
m_NewQuery = true;
#endif
Idx -= 1;
dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds");
m_aStmtParameterExtras[Idx].f = Value;
MYSQL_BIND *pParam = &m_aStmtParameters[Idx];
pParam->buffer_type = MYSQL_TYPE_FLOAT;
pParam->buffer = &m_aStmtParameterExtras[Idx].f;
pParam->buffer_length = sizeof(m_aStmtParameterExtras[Idx].i);
pParam->length = nullptr;
pParam->is_null = nullptr;
pParam->is_unsigned = false;
pParam->error = nullptr;
}
bool CMysqlConnection::Step()
{
#if defined(CONF_SQL)
if(m_NewQuery)
{
m_NewQuery = false;
m_pResults.reset(m_pPreparedStmt->executeQuery());
if(mysql_stmt_bind_param(m_pStmt.get(), &m_aStmtParameters[0]))
{
StoreErrorStmt("bind_param");
dbg_msg("mysql", "error binding params %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: error handling
}
if(mysql_stmt_execute(m_pStmt.get()))
{
StoreErrorStmt("execute");
dbg_msg("mysql", "error executing query %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: error handling
return false;
}
}
return m_pResults->next();
#else
return false;
#endif
int Result = mysql_stmt_fetch(m_pStmt.get());
if(Result == 1)
{
StoreErrorStmt("fetch");
dbg_msg("mysql", "error fetching row %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: error handling
return false;
}
if(Result == MYSQL_NO_DATA)
{
return false;
}
// `Result` is now either `MYSQL_DATA_TRUNCATED` (which we ignore, we
// fetch our columns in a different way) or `0` aka success.
return true;
}
int CMysqlConnection::ExecuteUpdate()
{
#if defined(CONF_SQL)
if(m_NewQuery)
{
m_NewQuery = false;
return m_pPreparedStmt->executeUpdate();
if(mysql_stmt_bind_param(m_pStmt.get(), &m_aStmtParameters[0]))
{
StoreErrorStmt("bind_param");
dbg_msg("mysql", "error binding params %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: error handling
return -1;
}
if(mysql_stmt_execute(m_pStmt.get()))
{
StoreErrorStmt("execute");
dbg_msg("mysql", "error executing update %s", m_aErrorDetail);
throw std::runtime_error(m_aErrorDetail);
// TODO: error handling
return -1;
}
return mysql_stmt_affected_rows(m_pStmt.get());
}
#endif
return -1;
}
bool CMysqlConnection::IsNull(int Col) const
bool CMysqlConnection::IsNull(int Col)
{
#if defined(CONF_SQL)
return m_pResults->isNull(Col);
#else
return false;
#endif
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) const
float CMysqlConnection::GetFloat(int Col)
{
#if defined(CONF_SQL)
return (float)m_pResults->getDouble(Col);
#else
return 0.0;
#endif
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;
}
int CMysqlConnection::GetInt(int Col) const
int CMysqlConnection::GetInt(int Col)
{
#if defined(CONF_SQL)
return m_pResults->getInt(Col);
#else
return 0;
#endif
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;
}
void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const
void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize)
{
#if defined(CONF_SQL)
auto String = m_pResults->getString(Col);
str_copy(pBuffer, String.c_str(), BufferSize);
#endif
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");
}
}
int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const
int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
{
#if defined(CONF_SQL)
auto *Blob = m_pResults->getBlob(Col);
Blob->read((char *)pBuffer, BufferSize);
int NumRead = Blob->gcount();
delete Blob;
return NumRead;
#else
return 0;
#endif
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;
}
const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const
@ -317,7 +648,7 @@ void CMysqlConnection::AddPoints(const char *pPlayer, int Points)
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_points(Name, Points) "
"VALUES (?, ?) "
"ON DUPLICATE KEY UPDATE Points=Points+?;",
"ON DUPLICATE KEY UPDATE Points=Points+?",
GetPrefix());
PrepareStatement(aBuf);
BindString(1, pPlayer);
@ -325,3 +656,35 @@ void CMysqlConnection::AddPoints(const char *pPlayer, int Points)
BindInt(3, Points);
Step();
}
IDbConnection *CreateMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup)
{
return new CMysqlConnection(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup);
}
#else
int MysqlInit()
{
return 0;
}
void MysqlUninit()
{
}
IDbConnection *CreateMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup)
{
return nullptr;
}
#endif

View file

@ -1,83 +0,0 @@
#ifndef ENGINE_SERVER_DATABASES_MYSQL_H
#define ENGINE_SERVER_DATABASES_MYSQL_H
#include <atomic>
#include <engine/server/databases/connection.h>
#include <memory>
class CLock;
namespace sql {
class Connection;
class PreparedStatement;
class ResultSet;
class Statement;
} /* namespace sql */
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);
virtual ~CMysqlConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CMysqlConnection *Copy();
virtual const char *BinaryCollate() const { return "utf8mb4_bin"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "?"; }
virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
virtual const char *InsertIgnore() const { return "INSERT IGNORE"; };
virtual const char *Random() const { return "RAND()"; };
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
virtual Status Connect();
virtual void Disconnect();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual void Print() {}
virtual bool Step();
virtual int ExecuteUpdate();
virtual bool IsNull(int Col) const;
virtual float GetFloat(int Col) const;
virtual int GetInt(int Col) const;
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual void AddPoints(const char *pPlayer, int Points);
private:
#if defined(CONF_SQL)
std::unique_ptr<sql::Connection> m_pConnection;
std::unique_ptr<sql::PreparedStatement> m_pPreparedStmt;
std::unique_ptr<sql::Statement> m_pStmt;
std::unique_ptr<sql::ResultSet> m_pResults;
bool m_NewQuery;
#endif
// 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;
static CLock m_SqlDriverLock;
};
#endif // ENGINE_SERVER_DATABASES_MYSQL_H

View file

@ -1,11 +1,69 @@
#include "sqlite.h"
#include "connection.h"
#include <sqlite3.h>
#include <base/math.h>
#include <engine/console.h>
#include <sqlite3.h>
#include <atomic>
#include <stdexcept>
class CSqliteConnection : public IDbConnection
{
public:
CSqliteConnection(const char *pFilename, bool Setup);
virtual ~CSqliteConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CSqliteConnection *Copy();
virtual const char *BinaryCollate() const { return "BINARY"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; }
virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; }
virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; };
virtual const char *Random() const { return "RANDOM()"; };
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
virtual Status Connect();
virtual void Disconnect();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual void Print();
virtual bool Step();
virtual int ExecuteUpdate();
virtual bool IsNull(int Col);
virtual float GetFloat(int Col);
virtual int GetInt(int Col);
virtual void GetString(int Col, char *pBuffer, int BufferSize);
// passing a negative buffer size is undefined behavior
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize);
virtual void AddPoints(const char *pPlayer, int Points);
private:
// copy of config vars
char m_aFilename[512];
bool m_Setup;
sqlite3 *m_pDb;
sqlite3_stmt *m_pStmt;
bool m_Done; // no more rows available for Step
// returns true, if the query succeeded
bool Execute(const char *pQuery);
void ExceptionOnError(int Result);
std::atomic_bool m_InUse;
};
CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) :
IDbConnection("record"),
m_Setup(Setup),
@ -178,27 +236,27 @@ int CSqliteConnection::ExecuteUpdate()
return sqlite3_changes(m_pDb);
}
bool CSqliteConnection::IsNull(int Col) const
bool CSqliteConnection::IsNull(int Col)
{
return sqlite3_column_type(m_pStmt, Col - 1) == SQLITE_NULL;
}
float CSqliteConnection::GetFloat(int Col) const
float CSqliteConnection::GetFloat(int Col)
{
return (float)sqlite3_column_double(m_pStmt, Col - 1);
}
int CSqliteConnection::GetInt(int Col) const
int CSqliteConnection::GetInt(int Col)
{
return sqlite3_column_int(m_pStmt, Col - 1);
}
void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) const
void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize)
{
str_copy(pBuffer, (const char *)sqlite3_column_text(m_pStmt, Col - 1), BufferSize);
}
int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const
int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
{
int Size = sqlite3_column_bytes(m_pStmt, Col - 1);
Size = minimum(Size, BufferSize);
@ -257,3 +315,8 @@ void CSqliteConnection::AddPoints(const char *pPlayer, int Points)
BindInt(3, Points);
Step();
}
IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup)
{
return new CSqliteConnection(pFilename, Setup);
}

View file

@ -1,66 +0,0 @@
#ifndef ENGINE_SERVER_DATABASES_SQLITE_H
#define ENGINE_SERVER_DATABASES_SQLITE_H
#include "connection.h"
#include <atomic>
struct sqlite3;
struct sqlite3_stmt;
class CSqliteConnection : public IDbConnection
{
public:
CSqliteConnection(const char *pFilename, bool Setup);
virtual ~CSqliteConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CSqliteConnection *Copy();
virtual const char *BinaryCollate() const { return "BINARY"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; }
virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; }
virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; };
virtual const char *Random() const { return "RANDOM()"; };
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
virtual Status Connect();
virtual void Disconnect();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual void Print();
virtual bool Step();
virtual int ExecuteUpdate();
virtual bool IsNull(int Col) const;
virtual float GetFloat(int Col) const;
virtual int GetInt(int Col) const;
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
// passing a negative buffer size is undefined behavior
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual void AddPoints(const char *pPlayer, int Points);
private:
// copy of config vars
char m_aFilename[512];
bool m_Setup;
sqlite3 *m_pDb;
sqlite3_stmt *m_pStmt;
bool m_Done; // no more rows available for Step
// returns true, if the query succeeded
bool Execute(const char *pQuery);
void ExceptionOnError(int Result);
std::atomic_bool m_InUse;
};
#endif // ENGINE_SERVER_DATABASES_SQLITE_H

View file

@ -3,6 +3,8 @@
#define _WIN32_WINNT 0x0501
#include "server.h"
#include <base/math.h>
#include <base/system.h>
@ -35,18 +37,15 @@
#include <vector>
#include <zlib.h>
#include "databases/connection.h"
#include "databases/connection_pool.h"
#include "register.h"
#include "server.h"
#if defined(CONF_FAMILY_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <engine/server/databases/connection_pool.h>
#include <engine/server/databases/mysql.h>
#include <engine/server/databases/sqlite.h>
CSnapIDPool::CSnapIDPool()
{
Reset();
@ -2358,7 +2357,7 @@ int CServer::Run()
if(g_Config.m_SvSqliteFile[0] != '\0')
{
auto pSqlServers = std::unique_ptr<CSqliteConnection>(new CSqliteConnection(
auto pSqlServers = std::unique_ptr<IDbConnection>(CreateSqliteConnection(
g_Config.m_SvSqliteFile, true));
if(g_Config.m_SvUseSQL)
@ -2367,7 +2366,7 @@ int CServer::Run()
}
else
{
auto pCopy = std::unique_ptr<CSqliteConnection>(pSqlServers->Copy());
auto pCopy = std::unique_ptr<IDbConnection>(pSqlServers->Copy());
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ);
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
}
@ -3166,14 +3165,20 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
auto pSqlServers = std::unique_ptr<CMysqlConnection>(new CMysqlConnection(
auto pSqlServers = std::unique_ptr<IDbConnection>(CreateMysqlConnection(
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6),
SetUpDb));
if(!pSqlServers)
{
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support");
return;
}
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
"Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d",
ReadOnly ? "Read" : "Write",
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
pResult->GetString(5), pResult->GetInteger(6));
@ -3483,6 +3488,11 @@ int main(int argc, const char **argv) // ignore_convention
dbg_msg("secure", "could not initialize secure RNG");
return -1;
}
if(MysqlInit() != 0)
{
dbg_msg("mysql", "failed to initialize MySQL library");
return -1;
}
CServer *pServer = CreateServer();
IKernel *pKernel = IKernel::Create();
@ -3556,6 +3566,8 @@ int main(int argc, const char **argv) // ignore_convention
dbg_msg("server", "starting...");
int Ret = pServer->Run();
MysqlUninit();
// free
delete pKernel;