Add SQLite interface

This commit is contained in:
Zwelf 2020-07-07 19:29:44 +02:00
parent ad21ec1269
commit 7c31a15c93
6 changed files with 276 additions and 1 deletions

View file

@ -337,6 +337,7 @@ endif()
find_package(Pnglite) find_package(Pnglite)
find_package(PythonInterp 3) find_package(PythonInterp 3)
find_package(SDL2) find_package(SDL2)
find_package(SQLite3)
if(VIDEORECORDER) if(VIDEORECORDER)
find_package(FFMPEG) find_package(FFMPEG)
endif() endif()
@ -417,6 +418,7 @@ endif()
show_dependency_status("Pnglite" PNGLITE) show_dependency_status("Pnglite" PNGLITE)
show_dependency_status("PythonInterp" PYTHONINTERP) show_dependency_status("PythonInterp" PYTHONINTERP)
show_dependency_status("SDL2" SDL2) show_dependency_status("SDL2" SDL2)
show_dependency_status("SQLite3" SQLite3)
if(VIDEORECORDER) if(VIDEORECORDER)
show_dependency_status("FFmpeg" FFMPEG) show_dependency_status("FFmpeg" FFMPEG)
endif() endif()
@ -432,6 +434,9 @@ endif()
if(NOT(PYTHONINTERP_FOUND)) if(NOT(PYTHONINTERP_FOUND))
message(SEND_ERROR "You must install Python to compile DDNet") message(SEND_ERROR "You must install Python to compile DDNet")
endif() endif()
if(NOT(SQLite3_FOUND))
message(SEND_ERROR "You must install SQLite3 to compile the DDNet server")
endif()
if(MYSQL AND NOT(MYSQL_FOUND)) if(MYSQL AND NOT(MYSQL_FOUND))
message(SEND_ERROR "You must install MySQL to compile the DDNet server with MySQL support") message(SEND_ERROR "You must install MySQL to compile the DDNet server with MySQL support")
@ -1844,6 +1849,8 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server
databases/connection_pool.h databases/connection_pool.h
databases/mysql.cpp databases/mysql.cpp
databases/mysql.h databases/mysql.h
databases/sqlite.cpp
databases/sqlite.h
name_ban.cpp name_ban.cpp
name_ban.h name_ban.h
register.cpp register.cpp
@ -1931,6 +1938,7 @@ endif()
set(LIBS_SERVER set(LIBS_SERVER
${LIBS} ${LIBS}
${MYSQL_LIBRARIES} ${MYSQL_LIBRARIES}
${SQLite3_LIBRARIES}
${TARGET_ANTIBOT} ${TARGET_ANTIBOT}
${MINIUPNPC_LIBRARIES} ${MINIUPNPC_LIBRARIES}
# Add pthreads (on non-Windows) at the end, so that other libraries can depend # Add pthreads (on non-Windows) at the end, so that other libraries can depend

View file

@ -3,6 +3,7 @@
#if defined(CONF_SQL) #if defined(CONF_SQL)
#include <cppconn/exception.h> #include <cppconn/exception.h>
#endif #endif
#include <stdexcept>
// helper struct to hold thread data // helper struct to hold thread data
struct CSqlExecData struct CSqlExecData
@ -176,6 +177,10 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD
dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "MySQL Error: %s", e.what());
} }
#endif #endif
catch (std::runtime_error &e)
{
dbg_msg("sql", "SQLite Error: %s", e.what());
}
catch (...) catch (...)
{ {
dbg_msg("sql", "Unexpected exception caught"); dbg_msg("sql", "Unexpected exception caught");

View file

@ -0,0 +1,186 @@
#include "sqlite.h"
#include <base/math.h>
#include <engine/shared/protocol.h>
#include <stdexcept>
#include <sqlite3.h>
CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) :
IDbConnection("record"),
m_Setup(Setup),
m_pDb(nullptr),
m_pStmt(nullptr),
m_Done(true),
m_InUse(false)
{
str_copy(m_aFilename, pFilename, sizeof(m_aFilename));
}
CSqliteConnection::~CSqliteConnection()
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
sqlite3_close(m_pDb);
m_pDb = nullptr;
}
CSqliteConnection *CSqliteConnection::Copy()
{
return new CSqliteConnection(m_aFilename, m_Setup);
}
IDbConnection::Status CSqliteConnection::Connect()
{
if(m_InUse.exchange(true))
return Status::IN_USE;
if(m_pDb != nullptr)
return Status::SUCCESS;
int Result = sqlite3_open(m_aFilename, &m_pDb);
if(Result != SQLITE_OK)
{
dbg_msg("sql", "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb));
return Status::ERROR;
}
// wait for database to unlock so we don't have to handle SQLITE_BUSY errors
sqlite3_busy_timeout(m_pDb, -1);
if(m_Setup)
{
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), m_pCreateRace, GetPrefix(), MAX_NAME_LENGTH);
if(!Execute(aBuf))
return Status::ERROR;
str_format(aBuf, sizeof(aBuf), m_pCreateTeamrace, GetPrefix(), MAX_NAME_LENGTH);
if(!Execute(aBuf))
return Status::ERROR;
str_format(aBuf, sizeof(aBuf), m_pCreateMaps, GetPrefix());
if(!Execute(aBuf))
return Status::ERROR;
str_format(aBuf, sizeof(aBuf), m_pCreateSaves, GetPrefix());
if(!Execute(aBuf))
return Status::ERROR;
str_format(aBuf, sizeof(aBuf), m_pCreatePoints, GetPrefix(), MAX_NAME_LENGTH);
if(!Execute(aBuf))
return Status::ERROR;
m_Setup = false;
}
return Status::SUCCESS;
}
void CSqliteConnection::Disconnect()
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
m_pStmt = nullptr;
m_InUse.store(false);
}
void CSqliteConnection::Lock(const char *pTable)
{
}
void CSqliteConnection::Unlock()
{
}
void CSqliteConnection::PrepareStatement(const char *pStmt)
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
m_pStmt = nullptr;
int Result = sqlite3_prepare_v2(
m_pDb,
pStmt,
-1, // pStmt can be any length
&m_pStmt,
NULL);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindString(int Idx, const char *pString)
{
int Result = sqlite3_bind_text(m_pStmt, Idx, pString, -1, NULL);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindInt(int Idx, int Value)
{
int Result = sqlite3_bind_int(m_pStmt, Idx, Value);
ExceptionOnError(Result);
m_Done = false;
}
bool CSqliteConnection::Step()
{
if(m_Done)
return false;
int Result = sqlite3_step(m_pStmt);
if(Result == SQLITE_ROW)
{
return true;
}
else if(Result == SQLITE_DONE)
{
m_Done = true;
return false;
}
else
{
ExceptionOnError(Result);
}
return false;
}
bool CSqliteConnection::IsNull(int Col) const
{
return sqlite3_column_type(m_pStmt, Col - 1) == SQLITE_NULL;
}
float CSqliteConnection::GetFloat(int Col) const
{
return (float)sqlite3_column_double(m_pStmt, Col - 1);
}
int CSqliteConnection::GetInt(int Col) const
{
return sqlite3_column_int(m_pStmt, Col - 1);
}
void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) const
{
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 Size = sqlite3_column_bytes(m_pStmt, Col - 1);
Size = minimum(Size, BufferSize);
mem_copy(pBuffer, sqlite3_column_blob(m_pStmt, Col - 1), Size);
return Size;
}
bool CSqliteConnection::Execute(const char *pQuery)
{
char *pErrorMsg;
int Result = sqlite3_exec(m_pDb, pQuery, NULL, NULL, &pErrorMsg);
if(Result != SQLITE_OK)
{
dbg_msg("sql", "error executing query: '%s'", pErrorMsg);
sqlite3_free(pErrorMsg);
}
return Result == SQLITE_OK;
}
void CSqliteConnection::ExceptionOnError(int Result)
{
if(Result != SQLITE_OK)
{
throw std::runtime_error(sqlite3_errmsg(m_pDb));
}
}

View file

@ -0,0 +1,54 @@
#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 CSqliteConnection *Copy();
virtual Status Connect();
virtual void Disconnect();
virtual void Lock(const char *pTable);
virtual void Unlock();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindInt(int Idx, int Value);
virtual bool Step();
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;
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 succeded
bool Execute(const char *pQuery);
void ExceptionOnError(int Result);
std::atomic_bool m_InUse;
};
#endif // ENGINE_SERVER_DATABASES_SQLITE_H

View file

@ -45,6 +45,7 @@
#endif #endif
#include <engine/server/databases/mysql.h> #include <engine/server/databases/mysql.h>
#include <engine/server/databases/sqlite.h>
#include <engine/server/databases/connection_pool.h> #include <engine/server/databases/connection_pool.h>
@ -2301,6 +2302,23 @@ int CServer::Run()
return -1; return -1;
} }
if(g_Config.m_SvSqliteFile[0] != '\0')
{
auto pSqlServers = std::unique_ptr<CSqliteConnection>(new CSqliteConnection(
g_Config.m_SvSqliteFile, true));
if(g_Config.m_SvUseSQL)
{
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::WRITE_BACKUP);
}
else
{
auto pCopy = std::unique_ptr<CSqliteConnection>(pSqlServers->Copy());
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ);
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
}
}
// start server // start server
NETADDR BindAddr; NETADDR BindAddr;
int NetType = g_Config.m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL; int NetType = g_Config.m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL;
@ -3051,6 +3069,8 @@ void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser) void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
{ {
if(!g_Config.m_SvUseSQL)
return;
CServer *pServer = (CServer *)pUser; CServer *pServer = (CServer *)pUser;
if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS &&
@ -3072,6 +3092,8 @@ void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
{ {
if(!g_Config.m_SvUseSQL)
return;
CServer *pSelf = (CServer *)pUserData; CServer *pSelf = (CServer *)pUserData;
if (pResult->NumArguments() != 7 && pResult->NumArguments() != 8) if (pResult->NumArguments() != 7 && pResult->NumArguments() != 8)

View file

@ -219,7 +219,7 @@ MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables sa
MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame") MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame")
MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file") MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file")
MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player") MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player")
MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off") MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server")
#if defined(CONF_UPNP) #if defined(CONF_UPNP)
MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.") MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.")