From 7c31a15c93b34bd0557039ded65ea9456a72e33b Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 7 Jul 2020 19:29:44 +0200 Subject: [PATCH] Add SQLite interface --- CMakeLists.txt | 8 + .../server/databases/connection_pool.cpp | 5 + src/engine/server/databases/sqlite.cpp | 186 ++++++++++++++++++ src/engine/server/databases/sqlite.h | 54 +++++ src/engine/server/server.cpp | 22 +++ src/engine/shared/config_variables.h | 2 +- 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/engine/server/databases/sqlite.cpp create mode 100644 src/engine/server/databases/sqlite.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d1cde6fbc..8e98b1944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index 5ff648fcc..98f8400b4 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -3,6 +3,7 @@ #if defined(CONF_SQL) #include #endif +#include // 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"); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp new file mode 100644 index 000000000..2d7105c22 --- /dev/null +++ b/src/engine/server/databases/sqlite.cpp @@ -0,0 +1,186 @@ +#include "sqlite.h" + +#include +#include + +#include +#include + +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)); + } +} diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h new file mode 100644 index 000000000..d080472ee --- /dev/null +++ b/src/engine/server/databases/sqlite.h @@ -0,0 +1,54 @@ +#ifndef ENGINE_SERVER_DATABASES_SQLITE_H +#define ENGINE_SERVER_DATABASES_SQLITE_H + +#include "connection.h" +#include + +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 diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index cd7a5634c..86039638e 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -45,6 +45,7 @@ #endif #include +#include #include @@ -2301,6 +2302,23 @@ int CServer::Run() return -1; } + if(g_Config.m_SvSqliteFile[0] != '\0') + { + auto pSqlServers = std::unique_ptr(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(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) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index b4b19f01d..4d59d3e50 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -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.")