mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Add SQLite interface
This commit is contained in:
parent
ad21ec1269
commit
7c31a15c93
|
@ -337,6 +337,7 @@ endif()
|
|||
find_package(Pnglite)
|
||||
find_package(PythonInterp 3)
|
||||
find_package(SDL2)
|
||||
find_package(SQLite3)
|
||||
if(VIDEORECORDER)
|
||||
find_package(FFMPEG)
|
||||
endif()
|
||||
|
@ -417,6 +418,7 @@ endif()
|
|||
show_dependency_status("Pnglite" PNGLITE)
|
||||
show_dependency_status("PythonInterp" PYTHONINTERP)
|
||||
show_dependency_status("SDL2" SDL2)
|
||||
show_dependency_status("SQLite3" SQLite3)
|
||||
if(VIDEORECORDER)
|
||||
show_dependency_status("FFmpeg" FFMPEG)
|
||||
endif()
|
||||
|
@ -432,6 +434,9 @@ endif()
|
|||
if(NOT(PYTHONINTERP_FOUND))
|
||||
message(SEND_ERROR "You must install Python to compile DDNet")
|
||||
endif()
|
||||
if(NOT(SQLite3_FOUND))
|
||||
message(SEND_ERROR "You must install SQLite3 to compile the DDNet server")
|
||||
endif()
|
||||
|
||||
if(MYSQL AND NOT(MYSQL_FOUND))
|
||||
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/mysql.cpp
|
||||
databases/mysql.h
|
||||
databases/sqlite.cpp
|
||||
databases/sqlite.h
|
||||
name_ban.cpp
|
||||
name_ban.h
|
||||
register.cpp
|
||||
|
@ -1931,6 +1938,7 @@ endif()
|
|||
set(LIBS_SERVER
|
||||
${LIBS}
|
||||
${MYSQL_LIBRARIES}
|
||||
${SQLite3_LIBRARIES}
|
||||
${TARGET_ANTIBOT}
|
||||
${MINIUPNPC_LIBRARIES}
|
||||
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#if defined(CONF_SQL)
|
||||
#include <cppconn/exception.h>
|
||||
#endif
|
||||
#include <stdexcept>
|
||||
|
||||
// helper struct to hold thread data
|
||||
struct CSqlExecData
|
||||
|
@ -176,6 +177,10 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD
|
|||
dbg_msg("sql", "MySQL Error: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
catch (std::runtime_error &e)
|
||||
{
|
||||
dbg_msg("sql", "SQLite Error: %s", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
dbg_msg("sql", "Unexpected exception caught");
|
||||
|
|
186
src/engine/server/databases/sqlite.cpp
Normal file
186
src/engine/server/databases/sqlite.cpp
Normal 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));
|
||||
}
|
||||
}
|
54
src/engine/server/databases/sqlite.h
Normal file
54
src/engine/server/databases/sqlite.h
Normal 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
|
|
@ -45,6 +45,7 @@
|
|||
#endif
|
||||
|
||||
#include <engine/server/databases/mysql.h>
|
||||
#include <engine/server/databases/sqlite.h>
|
||||
#include <engine/server/databases/connection_pool.h>
|
||||
|
||||
|
||||
|
@ -2301,6 +2302,23 @@ int CServer::Run()
|
|||
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
|
||||
NETADDR BindAddr;
|
||||
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)
|
||||
{
|
||||
if(!g_Config.m_SvUseSQL)
|
||||
return;
|
||||
CServer *pServer = (CServer *)pUser;
|
||||
|
||||
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)
|
||||
{
|
||||
if(!g_Config.m_SvUseSQL)
|
||||
return;
|
||||
CServer *pSelf = (CServer *)pUserData;
|
||||
|
||||
if (pResult->NumArguments() != 7 && pResult->NumArguments() != 8)
|
||||
|
|
|
@ -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(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_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)
|
||||
MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.")
|
||||
|
|
Loading…
Reference in a new issue