mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-19 14:38:18 +00:00
Merge #5948
5948: Pr failsafe mysql r=def- a=Zwelf Fixes #4424 * Immediately store all ranks/teamranks/saves in separate tabel (suffixed with `_backup`) in sqlite * remove the rank if mysql succeeds * move the rank to non-`_backup` table when failed * loading save codes from non-`backup`-tables is still possible (mostly the reason for the backup table to exist: to prevent double loading) * Immediately tell player how the backup save code will be on save failure ![new_save_text](https://user-images.githubusercontent.com/9964758/196011192-34b0d014-4ac3-45c4-aab6-14f4cd135279.png) * It uses another thread (backup-thread) which goes through all changes first and writes ranks/teamranks/saves into the database * Enables `WAL`-mode in sqlite, because of busy-errors during opening the files (we open it every time we process a new rank/...) * Made database code more thread safe (especially adding new databases) I mostly started appending commits. I can squash them if it makes it more readable. Sorry that this PR is a big change. I will go through it tomorrow again to improve minor stuff. But review is also already possible. Naming of some things might be off, happy to hear suggestions. ## Checklist - [x] Tested the change ingame - [x] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test (especially base/) or added coverage to integration test - [ ] Considered possible null pointers and out of bounds array indexing - [x] Changed no physics that affect existing maps - [x] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Zwelf <zwelf@strct.cc>
This commit is contained in:
commit
4d6ef2fab6
|
@ -934,8 +934,12 @@ void sphore_init(SEMAPHORE *sem)
|
||||||
|
|
||||||
void sphore_wait(SEMAPHORE *sem)
|
void sphore_wait(SEMAPHORE *sem)
|
||||||
{
|
{
|
||||||
if(sem_wait(sem) != 0)
|
do
|
||||||
dbg_msg("sphore", "wait failed: %d", errno);
|
{
|
||||||
|
errno = 0;
|
||||||
|
if(sem_wait(sem) != 0)
|
||||||
|
dbg_msg("sphore", "wait failed: %d", errno);
|
||||||
|
} while(errno == EINTR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sphore_signal(SEMAPHORE *sem)
|
void sphore_signal(SEMAPHORE *sem)
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
#include <engine/shared/protocol.h>
|
#include <engine/shared/protocol.h>
|
||||||
|
|
||||||
void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize)
|
void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup)
|
||||||
{
|
{
|
||||||
str_format(aBuf, BufferSize,
|
str_format(aBuf, BufferSize,
|
||||||
"CREATE TABLE IF NOT EXISTS %s_race ("
|
"CREATE TABLE IF NOT EXISTS %s_race%s ("
|
||||||
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
||||||
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
||||||
" Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
" Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
||||||
|
@ -24,13 +24,14 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize)
|
||||||
" DDNet7 BOOL DEFAULT FALSE, "
|
" DDNet7 BOOL DEFAULT FALSE, "
|
||||||
" PRIMARY KEY (Map, Name, Time, Timestamp, Server)"
|
" PRIMARY KEY (Map, Name, Time, Timestamp, Server)"
|
||||||
")",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
|
GetPrefix(), Backup ? "_backup" : "",
|
||||||
|
BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType)
|
void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup)
|
||||||
{
|
{
|
||||||
str_format(aBuf, BufferSize,
|
str_format(aBuf, BufferSize,
|
||||||
"CREATE TABLE IF NOT EXISTS %s_teamrace ("
|
"CREATE TABLE IF NOT EXISTS %s_teamrace%s ("
|
||||||
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
||||||
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
||||||
" Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
" Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
||||||
|
@ -40,7 +41,8 @@ void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, co
|
||||||
" DDNet7 BOOL DEFAULT FALSE, "
|
" DDNet7 BOOL DEFAULT FALSE, "
|
||||||
" PRIMARY KEY (ID, Name)"
|
" PRIMARY KEY (ID, Name)"
|
||||||
")",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
|
GetPrefix(), Backup ? "_backup" : "",
|
||||||
|
BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
|
void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
|
||||||
|
@ -58,10 +60,10 @@ void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
|
||||||
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize)
|
void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup)
|
||||||
{
|
{
|
||||||
str_format(aBuf, BufferSize,
|
str_format(aBuf, BufferSize,
|
||||||
"CREATE TABLE IF NOT EXISTS %s_saves ("
|
"CREATE TABLE IF NOT EXISTS %s_saves%s ("
|
||||||
" Savegame TEXT COLLATE %s NOT NULL, "
|
" Savegame TEXT COLLATE %s NOT NULL, "
|
||||||
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
" Map VARCHAR(128) COLLATE %s NOT NULL, "
|
||||||
" Code VARCHAR(128) COLLATE %s NOT NULL, "
|
" Code VARCHAR(128) COLLATE %s NOT NULL, "
|
||||||
|
@ -71,7 +73,8 @@ void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize)
|
||||||
" SaveID VARCHAR(36) DEFAULT NULL, "
|
" SaveID VARCHAR(36) DEFAULT NULL, "
|
||||||
" PRIMARY KEY (Map, Code)"
|
" PRIMARY KEY (Map, Code)"
|
||||||
")",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
GetPrefix(), Backup ? "_backup" : "",
|
||||||
|
BinaryCollate(), BinaryCollate(), BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize)
|
void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef ENGINE_SERVER_DATABASES_CONNECTION_H
|
#ifndef ENGINE_SERVER_DATABASES_CONNECTION_H
|
||||||
#define ENGINE_SERVER_DATABASES_CONNECTION_H
|
#define ENGINE_SERVER_DATABASES_CONNECTION_H
|
||||||
|
|
||||||
|
#include "connection_pool.h"
|
||||||
#include <base/system.h>
|
#include <base/system.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -90,26 +91,19 @@ private:
|
||||||
char m_aPrefix[64];
|
char m_aPrefix[64];
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void FormatCreateRace(char *aBuf, unsigned int BufferSize);
|
void FormatCreateRace(char *aBuf, unsigned int BufferSize, bool Backup);
|
||||||
void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType);
|
void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType, bool Backup);
|
||||||
void FormatCreateMaps(char *aBuf, unsigned int BufferSize);
|
void FormatCreateMaps(char *aBuf, unsigned int BufferSize);
|
||||||
void FormatCreateSaves(char *aBuf, unsigned int BufferSize);
|
void FormatCreateSaves(char *aBuf, unsigned int BufferSize, bool Backup);
|
||||||
void FormatCreatePoints(char *aBuf, unsigned int BufferSize);
|
void FormatCreatePoints(char *aBuf, unsigned int BufferSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool MysqlAvailable();
|
||||||
int MysqlInit();
|
int MysqlInit();
|
||||||
void MysqlUninit();
|
void MysqlUninit();
|
||||||
|
|
||||||
std::unique_ptr<IDbConnection> CreateSqliteConnection(const char *pFilename, bool Setup);
|
std::unique_ptr<IDbConnection> CreateSqliteConnection(const char *pFilename, bool Setup);
|
||||||
// Returns nullptr if MySQL support is not compiled in.
|
// Returns nullptr if MySQL support is not compiled in.
|
||||||
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config);
|
||||||
const char *pDatabase,
|
|
||||||
const char *pPrefix,
|
|
||||||
const char *pUser,
|
|
||||||
const char *pPass,
|
|
||||||
const char *pIp,
|
|
||||||
const char *pBindaddr,
|
|
||||||
int Port,
|
|
||||||
bool Setup);
|
|
||||||
|
|
||||||
#endif // ENGINE_SERVER_DATABASES_CONNECTION_H
|
#endif // ENGINE_SERVER_DATABASES_CONNECTION_H
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
|
||||||
#include <base/system.h>
|
#include <base/system.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <engine/console.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
@ -21,17 +24,42 @@ struct CSqlExecData
|
||||||
CDbConnectionPool::FWrite pFunc,
|
CDbConnectionPool::FWrite pFunc,
|
||||||
std::unique_ptr<const ISqlData> pThreadData,
|
std::unique_ptr<const ISqlData> pThreadData,
|
||||||
const char *pName);
|
const char *pName);
|
||||||
|
CSqlExecData(
|
||||||
|
CDbConnectionPool::Mode m,
|
||||||
|
const char aFileName[64]);
|
||||||
|
CSqlExecData(
|
||||||
|
CDbConnectionPool::Mode m,
|
||||||
|
const CMysqlConfig *pMysqlConfig);
|
||||||
|
CSqlExecData(IConsole *pConsole, CDbConnectionPool::Mode m);
|
||||||
~CSqlExecData() = default;
|
~CSqlExecData() = default;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
READ_ACCESS,
|
READ_ACCESS,
|
||||||
WRITE_ACCESS,
|
WRITE_ACCESS,
|
||||||
|
ADD_MYSQL,
|
||||||
|
ADD_SQLITE,
|
||||||
|
PRINT,
|
||||||
} m_Mode;
|
} m_Mode;
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
CDbConnectionPool::FRead m_pReadFunc;
|
CDbConnectionPool::FRead m_pReadFunc;
|
||||||
CDbConnectionPool::FWrite m_pWriteFunc;
|
CDbConnectionPool::FWrite m_pWriteFunc;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
CDbConnectionPool::Mode m_Mode;
|
||||||
|
CMysqlConfig m_Config;
|
||||||
|
} m_MySql;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
CDbConnectionPool::Mode m_Mode;
|
||||||
|
char m_FileName[64];
|
||||||
|
} m_Sqlite;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
IConsole *m_pConsole;
|
||||||
|
CDbConnectionPool::Mode m_Mode;
|
||||||
|
} m_Print;
|
||||||
} m_Ptr;
|
} m_Ptr;
|
||||||
|
|
||||||
std::unique_ptr<const ISqlData> m_pThreadData;
|
std::unique_ptr<const ISqlData> m_pThreadData;
|
||||||
|
@ -60,30 +88,56 @@ CSqlExecData::CSqlExecData(
|
||||||
m_Ptr.m_pWriteFunc = pFunc;
|
m_Ptr.m_pWriteFunc = pFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
CDbConnectionPool::CDbConnectionPool() :
|
CSqlExecData::CSqlExecData(
|
||||||
|
CDbConnectionPool::Mode m,
|
||||||
m_FirstElem(0),
|
const char aFileName[64]) :
|
||||||
m_LastElem(0)
|
m_Mode(ADD_SQLITE),
|
||||||
|
m_pThreadData(nullptr),
|
||||||
|
m_pName("add sqlite server")
|
||||||
{
|
{
|
||||||
thread_init_and_detach(CDbConnectionPool::Worker, this, "database worker thread");
|
m_Ptr.m_Sqlite.m_Mode = m;
|
||||||
|
mem_copy(m_Ptr.m_Sqlite.m_FileName, aFileName, sizeof(m_Ptr.m_Sqlite.m_FileName));
|
||||||
|
}
|
||||||
|
CSqlExecData::CSqlExecData(CDbConnectionPool::Mode m,
|
||||||
|
const CMysqlConfig *pMysqlConfig) :
|
||||||
|
m_Mode(ADD_MYSQL),
|
||||||
|
m_pThreadData(nullptr),
|
||||||
|
m_pName("add mysql server")
|
||||||
|
{
|
||||||
|
m_Ptr.m_MySql.m_Mode = m;
|
||||||
|
mem_copy(&m_Ptr.m_MySql.m_Config, pMysqlConfig, sizeof(m_Ptr.m_MySql.m_Config));
|
||||||
|
}
|
||||||
|
|
||||||
|
CSqlExecData::CSqlExecData(IConsole *pConsole, CDbConnectionPool::Mode m) :
|
||||||
|
m_Mode(PRINT),
|
||||||
|
m_pThreadData(nullptr),
|
||||||
|
m_pName("print database server")
|
||||||
|
{
|
||||||
|
m_Ptr.m_Print.m_pConsole = pConsole;
|
||||||
|
m_Ptr.m_Print.m_Mode = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
CDbConnectionPool::~CDbConnectionPool() = default;
|
CDbConnectionPool::~CDbConnectionPool() = default;
|
||||||
|
|
||||||
void CDbConnectionPool::Print(IConsole *pConsole, Mode DatabaseMode)
|
void CDbConnectionPool::Print(IConsole *pConsole, Mode DatabaseMode)
|
||||||
{
|
{
|
||||||
static const char *s_apModeDesc[] = {"Read", "Write", "WriteBackup"};
|
m_pShared->m_aQueries[m_InsertIdx++] = std::make_unique<CSqlExecData>(pConsole, DatabaseMode);
|
||||||
for(unsigned int i = 0; i < m_vvpDbConnections[DatabaseMode].size(); i++)
|
m_InsertIdx %= std::size(m_pShared->m_aQueries);
|
||||||
{
|
m_pShared->m_NumBackup.Signal();
|
||||||
m_vvpDbConnections[DatabaseMode][i]->Print(pConsole, s_apModeDesc[DatabaseMode]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::RegisterDatabase(std::unique_ptr<IDbConnection> pDatabase, Mode DatabaseMode)
|
void CDbConnectionPool::RegisterSqliteDatabase(Mode DatabaseMode, const char aFileName[64])
|
||||||
{
|
{
|
||||||
if(DatabaseMode < 0 || NUM_MODES <= DatabaseMode)
|
m_pShared->m_aQueries[m_InsertIdx++] = std::make_unique<CSqlExecData>(DatabaseMode, aFileName);
|
||||||
return;
|
m_InsertIdx %= std::size(m_pShared->m_aQueries);
|
||||||
m_vvpDbConnections[DatabaseMode].push_back(std::move(pDatabase));
|
m_pShared->m_NumBackup.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDbConnectionPool::RegisterMysqlDatabase(Mode DatabaseMode, const CMysqlConfig *pMysqlConfig)
|
||||||
|
{
|
||||||
|
m_pShared->m_aQueries[m_InsertIdx++] = std::make_unique<CSqlExecData>(DatabaseMode, pMysqlConfig);
|
||||||
|
m_InsertIdx %= std::size(m_pShared->m_aQueries);
|
||||||
|
m_pShared->m_NumBackup.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::Execute(
|
void CDbConnectionPool::Execute(
|
||||||
|
@ -91,9 +145,9 @@ void CDbConnectionPool::Execute(
|
||||||
std::unique_ptr<const ISqlData> pSqlRequestData,
|
std::unique_ptr<const ISqlData> pSqlRequestData,
|
||||||
const char *pName)
|
const char *pName)
|
||||||
{
|
{
|
||||||
m_aTasks[m_FirstElem++] = std::make_unique<CSqlExecData>(pFunc, std::move(pSqlRequestData), pName);
|
m_pShared->m_aQueries[m_InsertIdx++] = std::make_unique<CSqlExecData>(pFunc, std::move(pSqlRequestData), pName);
|
||||||
m_FirstElem %= std::size(m_aTasks);
|
m_InsertIdx %= std::size(m_pShared->m_aQueries);
|
||||||
m_NumElem.Signal();
|
m_pShared->m_NumBackup.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::ExecuteWrite(
|
void CDbConnectionPool::ExecuteWrite(
|
||||||
|
@ -101,17 +155,17 @@ void CDbConnectionPool::ExecuteWrite(
|
||||||
std::unique_ptr<const ISqlData> pSqlRequestData,
|
std::unique_ptr<const ISqlData> pSqlRequestData,
|
||||||
const char *pName)
|
const char *pName)
|
||||||
{
|
{
|
||||||
m_aTasks[m_FirstElem++] = std::make_unique<CSqlExecData>(pFunc, std::move(pSqlRequestData), pName);
|
m_pShared->m_aQueries[m_InsertIdx++] = std::make_unique<CSqlExecData>(pFunc, std::move(pSqlRequestData), pName);
|
||||||
m_FirstElem %= std::size(m_aTasks);
|
m_InsertIdx %= std::size(m_pShared->m_aQueries);
|
||||||
m_NumElem.Signal();
|
m_pShared->m_NumBackup.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::OnShutdown()
|
void CDbConnectionPool::OnShutdown()
|
||||||
{
|
{
|
||||||
m_Shutdown.store(true);
|
m_pShared->m_Shutdown.store(true);
|
||||||
m_NumElem.Signal();
|
m_pShared->m_NumBackup.Signal();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while(m_Shutdown.load())
|
while(m_pShared->m_Shutdown.load())
|
||||||
{
|
{
|
||||||
// print a log about every two seconds
|
// print a log about every two seconds
|
||||||
if(i % 20 == 0 && i > 0)
|
if(i % 20 == 0 && i > 0)
|
||||||
|
@ -123,57 +177,143 @@ void CDbConnectionPool::OnShutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::Worker(void *pUser)
|
// The backup worker thread looks at write queries and stores them
|
||||||
|
// in the sqilte database (WRITE_BACKUP). It skips over read queries.
|
||||||
|
// After processing the query, it gets passed on to the Worker thread.
|
||||||
|
// This is done to not loose ranks when the server shuts down before all
|
||||||
|
// queries are executed on the mysql server
|
||||||
|
class CBackup
|
||||||
{
|
{
|
||||||
CDbConnectionPool *pThis = (CDbConnectionPool *)pUser;
|
public:
|
||||||
pThis->Worker();
|
CBackup(std::shared_ptr<CDbConnectionPool::CSharedData> pShared) :
|
||||||
|
m_pShared(std::move(pShared)) {}
|
||||||
|
static void Start(void *pUser);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessQueries();
|
||||||
|
|
||||||
|
std::unique_ptr<IDbConnection> m_pWriteBackup;
|
||||||
|
|
||||||
|
std::shared_ptr<CDbConnectionPool::CSharedData> m_pShared;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
void CBackup::Start(void *pUser)
|
||||||
|
{
|
||||||
|
CBackup *pThis = (CBackup *)pUser;
|
||||||
|
pThis->ProcessQueries();
|
||||||
|
delete pThis;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDbConnectionPool::Worker()
|
void CBackup::ProcessQueries()
|
||||||
{
|
{
|
||||||
// remember last working server and try to connect to it first
|
for(int JobNum = 0;; JobNum++)
|
||||||
int ReadServer = 0;
|
|
||||||
int WriteServer = 0;
|
|
||||||
// enter fail mode when a sql request fails, skip read request during it and
|
|
||||||
// write to the backup database until all requests are handled
|
|
||||||
bool FailMode = false;
|
|
||||||
while(true)
|
|
||||||
{
|
{
|
||||||
if(FailMode && m_NumElem.GetApproximateValue() == 0)
|
m_pShared->m_NumBackup.Wait();
|
||||||
{
|
CSqlExecData *pThreadData = m_pShared->m_aQueries[JobNum % std::size(m_pShared->m_aQueries)].get();
|
||||||
FailMode = false;
|
|
||||||
}
|
|
||||||
m_NumElem.Wait();
|
|
||||||
auto pThreadData = std::move(m_aTasks[m_LastElem++]);
|
|
||||||
// work through all database jobs after OnShutdown is called before exiting the thread
|
// work through all database jobs after OnShutdown is called before exiting the thread
|
||||||
if(pThreadData == nullptr)
|
if(pThreadData == nullptr)
|
||||||
{
|
{
|
||||||
m_Shutdown.store(false);
|
m_pShared->m_NumWorker.Signal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pThreadData->m_Mode == CSqlExecData::ADD_SQLITE &&
|
||||||
|
pThreadData->m_Ptr.m_Sqlite.m_Mode == CDbConnectionPool::Mode::WRITE_BACKUP)
|
||||||
|
{
|
||||||
|
m_pWriteBackup = CreateSqliteConnection(pThreadData->m_Ptr.m_Sqlite.m_FileName, true);
|
||||||
|
}
|
||||||
|
else if(pThreadData->m_Mode == CSqlExecData::WRITE_ACCESS && m_pWriteBackup.get())
|
||||||
|
{
|
||||||
|
bool Success = CDbConnectionPool::ExecSqlFunc(m_pWriteBackup.get(), pThreadData, Write::BACKUP_FIRST);
|
||||||
|
dbg_msg("sql", "[%i] %s done on write backup database, Success=%i", JobNum, pThreadData->m_pName, Success);
|
||||||
|
}
|
||||||
|
m_pShared->m_NumWorker.Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the worker threads executes queries on mysql or sqlite. If we write on
|
||||||
|
// a mysql server and have a backup server configured, we'll remove the
|
||||||
|
// entry from the backup server after completing it on the write server.
|
||||||
|
// static void Worker(void *pUser);
|
||||||
|
class CWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CWorker(std::shared_ptr<CDbConnectionPool::CSharedData> pShared) :
|
||||||
|
m_pShared(std::move(pShared)) {}
|
||||||
|
static void Start(void *pUser);
|
||||||
|
void ProcessQueries();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Print(IConsole *pConsole, CDbConnectionPool::Mode DatabaseMode);
|
||||||
|
|
||||||
|
// There are two possible configurations
|
||||||
|
// * sqlite mode: There exists exactly one READ and the same WRITE server
|
||||||
|
// with no WRITE_BACKUP server
|
||||||
|
// * mysql mode: there can exist multiple READ server, there must be at
|
||||||
|
// most one WRITE server. The WRITE server for all DDNet
|
||||||
|
// Servers must be the same (to counteract double loads).
|
||||||
|
// There may be one WRITE_BACKUP sqlite server.
|
||||||
|
// This variable should only change, before the worker threads
|
||||||
|
std::vector<std::unique_ptr<IDbConnection>> m_vpReadConnections;
|
||||||
|
std::unique_ptr<IDbConnection> m_pWriteConnection;
|
||||||
|
std::unique_ptr<IDbConnection> m_pWriteBackup;
|
||||||
|
|
||||||
|
std::shared_ptr<CDbConnectionPool::CSharedData> m_pShared;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
void CWorker::Start(void *pUser)
|
||||||
|
{
|
||||||
|
CWorker *pThis = (CWorker *)pUser;
|
||||||
|
pThis->ProcessQueries();
|
||||||
|
delete pThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWorker::ProcessQueries()
|
||||||
|
{
|
||||||
|
// remember last working server and try to connect to it first
|
||||||
|
int ReadServer = 0;
|
||||||
|
// enter fail mode when a sql request fails, skip read request during it and
|
||||||
|
// write to the backup database until all requests are handled
|
||||||
|
bool FailMode = false;
|
||||||
|
for(int JobNum = 0;; JobNum++)
|
||||||
|
{
|
||||||
|
if(FailMode && m_pShared->m_NumWorker.GetApproximateValue() == 0)
|
||||||
|
{
|
||||||
|
FailMode = false;
|
||||||
|
}
|
||||||
|
m_pShared->m_NumWorker.Wait();
|
||||||
|
auto pThreadData = std::move(m_pShared->m_aQueries[JobNum % std::size(m_pShared->m_aQueries)]);
|
||||||
|
// work through all database jobs after OnShutdown is called before exiting the thread
|
||||||
|
if(pThreadData == nullptr)
|
||||||
|
{
|
||||||
|
m_pShared->m_Shutdown.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_LastElem %= std::size(m_aTasks);
|
|
||||||
bool Success = false;
|
bool Success = false;
|
||||||
switch(pThreadData->m_Mode)
|
switch(pThreadData->m_Mode)
|
||||||
{
|
{
|
||||||
case CSqlExecData::READ_ACCESS:
|
case CSqlExecData::READ_ACCESS:
|
||||||
{
|
{
|
||||||
for(int i = 0; i < (int)m_vvpDbConnections[Mode::READ].size(); i++)
|
for(size_t i = 0; i < m_vpReadConnections.size(); i++)
|
||||||
{
|
{
|
||||||
if(m_Shutdown)
|
if(m_pShared->m_Shutdown)
|
||||||
{
|
{
|
||||||
dbg_msg("sql", "%s dismissed read request during shutdown", pThreadData->m_pName);
|
dbg_msg("sql", "[%i] %s dismissed read request during shutdown", JobNum, pThreadData->m_pName);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(FailMode)
|
if(FailMode)
|
||||||
{
|
{
|
||||||
dbg_msg("sql", "%s dismissed read request during FailMode", pThreadData->m_pName);
|
dbg_msg("sql", "[%i] %s dismissed read request during FailMode", JobNum, pThreadData->m_pName);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int CurServer = (ReadServer + i) % (int)m_vvpDbConnections[Mode::READ].size();
|
int CurServer = (ReadServer + i) % (int)m_vpReadConnections.size();
|
||||||
if(ExecSqlFunc(m_vvpDbConnections[Mode::READ][CurServer].get(), pThreadData.get(), false))
|
if(CDbConnectionPool::ExecSqlFunc(m_vpReadConnections[CurServer].get(), pThreadData.get(), Write::NORMAL))
|
||||||
{
|
{
|
||||||
ReadServer = CurServer;
|
ReadServer = CurServer;
|
||||||
dbg_msg("sql", "%s done on read database %d", pThreadData->m_pName, CurServer);
|
dbg_msg("sql", "[%i] %s done on read database %d", JobNum, pThreadData->m_pName, CurServer);
|
||||||
Success = true;
|
Success = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -186,46 +326,81 @@ void CDbConnectionPool::Worker()
|
||||||
break;
|
break;
|
||||||
case CSqlExecData::WRITE_ACCESS:
|
case CSqlExecData::WRITE_ACCESS:
|
||||||
{
|
{
|
||||||
for(int i = 0; i < (int)m_vvpDbConnections[Mode::WRITE].size(); i++)
|
if(m_pShared->m_Shutdown && m_pWriteBackup != nullptr)
|
||||||
{
|
{
|
||||||
if(m_Shutdown && !m_vvpDbConnections[Mode::WRITE_BACKUP].empty())
|
dbg_msg("sql", "[%i] %s skipped to backup database during shutdown", JobNum, pThreadData->m_pName);
|
||||||
{
|
break;
|
||||||
dbg_msg("sql", "%s skipped to backup database during shutdown", pThreadData->m_pName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(FailMode && !m_vvpDbConnections[Mode::WRITE_BACKUP].empty())
|
|
||||||
{
|
|
||||||
dbg_msg("sql", "%s skipped to backup database during FailMode", pThreadData->m_pName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
int CurServer = (WriteServer + i) % (int)m_vvpDbConnections[Mode::WRITE].size();
|
|
||||||
if(ExecSqlFunc(m_vvpDbConnections[Mode::WRITE][i].get(), pThreadData.get(), false))
|
|
||||||
{
|
|
||||||
WriteServer = CurServer;
|
|
||||||
dbg_msg("sql", "%s done on write database %d", pThreadData->m_pName, CurServer);
|
|
||||||
Success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(!Success)
|
else if(FailMode && m_pWriteBackup != nullptr)
|
||||||
{
|
{
|
||||||
FailMode = true;
|
dbg_msg("sql", "[%i] %s skipped to backup database during FailMode", JobNum, pThreadData->m_pName);
|
||||||
for(int i = 0; i < (int)m_vvpDbConnections[Mode::WRITE_BACKUP].size(); i++)
|
break;
|
||||||
{
|
}
|
||||||
if(ExecSqlFunc(m_vvpDbConnections[Mode::WRITE_BACKUP][i].get(), pThreadData.get(), true))
|
else if(CDbConnectionPool::ExecSqlFunc(m_pWriteConnection.get(), pThreadData.get(), Write::NORMAL))
|
||||||
{
|
{
|
||||||
dbg_msg("sql", "%s done on write backup database %d", pThreadData->m_pName, i);
|
dbg_msg("sql", "[%i] %s done on write database", JobNum, pThreadData->m_pName);
|
||||||
Success = true;
|
Success = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
// enter fail mode if not successful
|
||||||
|
FailMode = FailMode || !Success;
|
||||||
|
const Write w = Success ? Write::NORMAL_SUCCEEDED : Write::NORMAL_FAILED;
|
||||||
|
if(CDbConnectionPool::ExecSqlFunc(m_pWriteBackup.get(), pThreadData.get(), w))
|
||||||
|
{
|
||||||
|
dbg_msg("sql", "[%i] %s done move write on backup database to non-backup table", JobNum, pThreadData->m_pName);
|
||||||
|
Success = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case CSqlExecData::ADD_MYSQL:
|
||||||
|
{
|
||||||
|
auto pMysql = CreateMysqlConnection(pThreadData->m_Ptr.m_MySql.m_Config);
|
||||||
|
switch(pThreadData->m_Ptr.m_MySql.m_Mode)
|
||||||
|
{
|
||||||
|
case CDbConnectionPool::Mode::READ:
|
||||||
|
m_vpReadConnections.push_back(std::move(pMysql));
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::WRITE:
|
||||||
|
m_pWriteConnection = std::move(pMysql);
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::WRITE_BACKUP:
|
||||||
|
m_pWriteBackup = std::move(pMysql);
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::NUM_MODES:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CSqlExecData::ADD_SQLITE:
|
||||||
|
{
|
||||||
|
auto pSqlite = CreateSqliteConnection(pThreadData->m_Ptr.m_Sqlite.m_FileName, true);
|
||||||
|
switch(pThreadData->m_Ptr.m_Sqlite.m_Mode)
|
||||||
|
{
|
||||||
|
case CDbConnectionPool::Mode::READ:
|
||||||
|
m_vpReadConnections.push_back(std::move(pSqlite));
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::WRITE:
|
||||||
|
m_pWriteConnection = std::move(pSqlite);
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::WRITE_BACKUP:
|
||||||
|
m_pWriteBackup = std::move(pSqlite);
|
||||||
|
break;
|
||||||
|
case CDbConnectionPool::Mode::NUM_MODES:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CSqlExecData::PRINT:
|
||||||
|
Print(pThreadData->m_Ptr.m_Print.m_pConsole, pThreadData->m_Ptr.m_Print.m_Mode);
|
||||||
|
Success = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if(!Success)
|
if(!Success)
|
||||||
dbg_msg("sql", "%s failed on all databases", pThreadData->m_pName);
|
dbg_msg("sql", "[%i] %s failed on all databases", JobNum, pThreadData->m_pName);
|
||||||
if(pThreadData->m_pThreadData->m_pResult != nullptr)
|
if(pThreadData->m_pThreadData != nullptr && pThreadData->m_pThreadData->m_pResult != nullptr)
|
||||||
{
|
{
|
||||||
pThreadData->m_pThreadData->m_pResult->m_Success = Success;
|
pThreadData->m_pThreadData->m_pResult->m_Success = Success;
|
||||||
pThreadData->m_pThreadData->m_pResult->m_Completed.store(true);
|
pThreadData->m_pThreadData->m_pResult->m_Completed.store(true);
|
||||||
|
@ -233,9 +408,40 @@ void CDbConnectionPool::Worker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pData, bool Failure)
|
void CWorker::Print(IConsole *pConsole, CDbConnectionPool::Mode DatabaseMode)
|
||||||
|
{
|
||||||
|
if(DatabaseMode == CDbConnectionPool::Mode::READ)
|
||||||
|
{
|
||||||
|
for(auto &pReadConnection : m_vpReadConnections)
|
||||||
|
pReadConnection->Print(pConsole, "Read");
|
||||||
|
if(m_vpReadConnections.empty())
|
||||||
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "There are no read databases");
|
||||||
|
}
|
||||||
|
else if(DatabaseMode == CDbConnectionPool::Mode::WRITE)
|
||||||
|
{
|
||||||
|
if(m_pWriteConnection)
|
||||||
|
m_pWriteConnection->Print(pConsole, "Write");
|
||||||
|
else
|
||||||
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "There are no write databases");
|
||||||
|
}
|
||||||
|
else if(DatabaseMode == CDbConnectionPool::Mode::WRITE_BACKUP)
|
||||||
|
{
|
||||||
|
if(m_pWriteBackup)
|
||||||
|
m_pWriteBackup->Print(pConsole, "WriteBackup");
|
||||||
|
else
|
||||||
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "There are no write backup databases");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pData, Write w)
|
||||||
{
|
{
|
||||||
char aError[256] = "error message not initialized";
|
char aError[256] = "error message not initialized";
|
||||||
|
if(pConnection == nullptr)
|
||||||
|
{
|
||||||
|
str_format(aError, sizeof(aError), "No database given");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if(pConnection->Connect(aError, sizeof(aError)))
|
if(pConnection->Connect(aError, sizeof(aError)))
|
||||||
{
|
{
|
||||||
dbg_msg("sql", "failed connecting to db: %s", aError);
|
dbg_msg("sql", "failed connecting to db: %s", aError);
|
||||||
|
@ -248,8 +454,10 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD
|
||||||
Success = !pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get(), aError, sizeof(aError));
|
Success = !pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get(), aError, sizeof(aError));
|
||||||
break;
|
break;
|
||||||
case CSqlExecData::WRITE_ACCESS:
|
case CSqlExecData::WRITE_ACCESS:
|
||||||
Success = !pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), Failure, aError, sizeof(aError));
|
Success = !pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), w, aError, sizeof(aError));
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
dbg_assert(false, "unreachable");
|
||||||
}
|
}
|
||||||
pConnection->Disconnect();
|
pConnection->Disconnect();
|
||||||
if(!Success)
|
if(!Success)
|
||||||
|
@ -258,3 +466,11 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD
|
||||||
}
|
}
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CDbConnectionPool::CDbConnectionPool()
|
||||||
|
{
|
||||||
|
m_pShared = std::make_shared<CSharedData>();
|
||||||
|
|
||||||
|
thread_init_and_detach(CWorker::Start, new CWorker(m_pShared), "database worker thread");
|
||||||
|
thread_init_and_detach(CBackup::Start, new CBackup(m_pShared), "database backup worker thread");
|
||||||
|
}
|
||||||
|
|
|
@ -26,13 +26,37 @@ struct ISqlData
|
||||||
m_pResult(std::move(pResult))
|
m_pResult(std::move(pResult))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
virtual ~ISqlData(){};
|
virtual ~ISqlData() = default;
|
||||||
|
|
||||||
mutable std::shared_ptr<ISqlResult> m_pResult;
|
mutable std::shared_ptr<ISqlResult> m_pResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Write
|
||||||
|
{
|
||||||
|
// write everything into the backup db first
|
||||||
|
BACKUP_FIRST,
|
||||||
|
// now try to write it into remote db
|
||||||
|
NORMAL,
|
||||||
|
// succeeded writing -> remove copy from backup
|
||||||
|
NORMAL_SUCCEEDED,
|
||||||
|
// failed writing -> notify about failure
|
||||||
|
NORMAL_FAILED,
|
||||||
|
};
|
||||||
|
|
||||||
class IConsole;
|
class IConsole;
|
||||||
|
|
||||||
|
struct CMysqlConfig
|
||||||
|
{
|
||||||
|
char m_aDatabase[64];
|
||||||
|
char m_aPrefix[64];
|
||||||
|
char m_aUser[64];
|
||||||
|
char m_aPass[64];
|
||||||
|
char m_aIp[64];
|
||||||
|
char m_aBindaddr[128];
|
||||||
|
int m_Port;
|
||||||
|
bool m_Setup;
|
||||||
|
};
|
||||||
|
|
||||||
class CDbConnectionPool
|
class CDbConnectionPool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -42,7 +66,7 @@ public:
|
||||||
|
|
||||||
// Returns false on success.
|
// Returns false on success.
|
||||||
typedef bool (*FRead)(IDbConnection *, const ISqlData *, char *pError, int ErrorSize);
|
typedef bool (*FRead)(IDbConnection *, const ISqlData *, char *pError, int ErrorSize);
|
||||||
typedef bool (*FWrite)(IDbConnection *, const ISqlData *, bool, char *pError, int ErrorSize);
|
typedef bool (*FWrite)(IDbConnection *, const ISqlData *, Write, char *pError, int ErrorSize);
|
||||||
|
|
||||||
enum Mode
|
enum Mode
|
||||||
{
|
{
|
||||||
|
@ -54,13 +78,15 @@ public:
|
||||||
|
|
||||||
void Print(IConsole *pConsole, Mode DatabaseMode);
|
void Print(IConsole *pConsole, Mode DatabaseMode);
|
||||||
|
|
||||||
void RegisterDatabase(std::unique_ptr<IDbConnection> pDatabase, Mode DatabaseMode);
|
void RegisterSqliteDatabase(Mode DatabaseMode, const char FileName[64]);
|
||||||
|
void RegisterMysqlDatabase(Mode DatabaseMode, const CMysqlConfig *pMysqlConfig);
|
||||||
|
|
||||||
void Execute(
|
void Execute(
|
||||||
FRead pFunc,
|
FRead pFunc,
|
||||||
std::unique_ptr<const ISqlData> pSqlRequestData,
|
std::unique_ptr<const ISqlData> pSqlRequestData,
|
||||||
const char *pName);
|
const char *pName);
|
||||||
// writes to WRITE_BACKUP server in case of failure
|
// writes to WRITE_BACKUP first and removes it from there when successfully
|
||||||
|
// executed on WRITE server
|
||||||
void ExecuteWrite(
|
void ExecuteWrite(
|
||||||
FWrite pFunc,
|
FWrite pFunc,
|
||||||
std::unique_ptr<const ISqlData> pSqlRequestData,
|
std::unique_ptr<const ISqlData> pSqlRequestData,
|
||||||
|
@ -68,18 +94,36 @@ public:
|
||||||
|
|
||||||
void OnShutdown();
|
void OnShutdown();
|
||||||
|
|
||||||
|
friend class CWorker;
|
||||||
|
friend class CBackup;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<IDbConnection>> m_vvpDbConnections[NUM_MODES];
|
static bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, Write w);
|
||||||
|
|
||||||
static void Worker(void *pUser);
|
// Only the main thread accesses this variable. It points to the index,
|
||||||
void Worker();
|
// where the next query is added to the queue.
|
||||||
bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, bool Failure);
|
int m_InsertIdx = 0;
|
||||||
|
|
||||||
std::atomic_bool m_Shutdown{false};
|
struct CSharedData
|
||||||
CSemaphore m_NumElem;
|
{
|
||||||
int m_FirstElem;
|
// Used as signal that shutdown is in progress from main thread to
|
||||||
int m_LastElem;
|
// speed up the queries by discarding read queries and writing to
|
||||||
std::unique_ptr<struct CSqlExecData> m_aTasks[512];
|
// the sqlite file instead of the remote mysql server.
|
||||||
|
// The worker thread signals the main thread that all queries are
|
||||||
|
// processed by setting this variable to false again.
|
||||||
|
std::atomic_bool m_Shutdown{false};
|
||||||
|
// Queries go first to the backup thread. This semaphore signals about
|
||||||
|
// new queries.
|
||||||
|
CSemaphore m_NumBackup;
|
||||||
|
// When the backup thread processed the query, it signals the main
|
||||||
|
// thread with this semaphore about the new query
|
||||||
|
CSemaphore m_NumWorker;
|
||||||
|
|
||||||
|
// spsc queue with additional backup worker to look at queries first.
|
||||||
|
std::unique_ptr<struct CSqlExecData> m_aQueries[512];
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<CSharedData> m_pShared;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ENGINE_SERVER_DATABASES_CONNECTION_POOL_H
|
#endif // ENGINE_SERVER_DATABASES_CONNECTION_POOL_H
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
#include "engine/server/databases/connection_pool.h"
|
||||||
|
|
||||||
#if defined(CONF_MYSQL)
|
#if defined(CONF_MYSQL)
|
||||||
#include <mysql.h>
|
#include <mysql.h>
|
||||||
|
@ -25,6 +26,11 @@ enum
|
||||||
std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED};
|
std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED};
|
||||||
std::atomic_int g_MysqlNumConnections;
|
std::atomic_int g_MysqlNumConnections;
|
||||||
|
|
||||||
|
bool MysqlAvailable()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int MysqlInit()
|
int MysqlInit()
|
||||||
{
|
{
|
||||||
dbg_assert(mysql_thread_safe(), "MySQL library without thread safety");
|
dbg_assert(mysql_thread_safe(), "MySQL library without thread safety");
|
||||||
|
@ -58,15 +64,7 @@ void MysqlUninit()
|
||||||
class CMysqlConnection : public IDbConnection
|
class CMysqlConnection : public IDbConnection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CMysqlConnection(
|
explicit CMysqlConnection(CMysqlConfig m_Config);
|
||||||
const char *pDatabase,
|
|
||||||
const char *pPrefix,
|
|
||||||
const char *pUser,
|
|
||||||
const char *pPass,
|
|
||||||
const char *pIp,
|
|
||||||
const char *pBindaddr,
|
|
||||||
int Port,
|
|
||||||
bool Setup);
|
|
||||||
~CMysqlConnection();
|
~CMysqlConnection();
|
||||||
void Print(IConsole *pConsole, const char *pMode) override;
|
void Print(IConsole *pConsole, const char *pMode) override;
|
||||||
|
|
||||||
|
@ -134,14 +132,8 @@ private:
|
||||||
std::vector<MYSQL_BIND> m_vStmtParameters;
|
std::vector<MYSQL_BIND> m_vStmtParameters;
|
||||||
std::vector<UParameterExtra> m_vStmtParameterExtras;
|
std::vector<UParameterExtra> m_vStmtParameterExtras;
|
||||||
|
|
||||||
// copy of config vars
|
// copy of m_Config vars
|
||||||
char m_aDatabase[64];
|
CMysqlConfig m_Config;
|
||||||
char m_aUser[64];
|
|
||||||
char m_aPass[64];
|
|
||||||
char m_aIp[64];
|
|
||||||
char m_aBindaddr[128];
|
|
||||||
int m_Port;
|
|
||||||
bool m_Setup;
|
|
||||||
|
|
||||||
std::atomic_bool m_InUse;
|
std::atomic_bool m_InUse;
|
||||||
};
|
};
|
||||||
|
@ -151,18 +143,9 @@ void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const
|
||||||
mysql_stmt_close(pStmt);
|
mysql_stmt_close(pStmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
CMysqlConnection::CMysqlConnection(
|
CMysqlConnection::CMysqlConnection(CMysqlConfig Config) :
|
||||||
const char *pDatabase,
|
IDbConnection(Config.m_aPrefix),
|
||||||
const char *pPrefix,
|
m_Config(Config),
|
||||||
const char *pUser,
|
|
||||||
const char *pPass,
|
|
||||||
const char *pIp,
|
|
||||||
const char *pBindaddr,
|
|
||||||
int Port,
|
|
||||||
bool Setup) :
|
|
||||||
IDbConnection(pPrefix),
|
|
||||||
m_Port(Port),
|
|
||||||
m_Setup(Setup),
|
|
||||||
m_InUse(false)
|
m_InUse(false)
|
||||||
{
|
{
|
||||||
g_MysqlNumConnections += 1;
|
g_MysqlNumConnections += 1;
|
||||||
|
@ -171,12 +154,6 @@ CMysqlConnection::CMysqlConnection(
|
||||||
mem_zero(m_aErrorDetail, sizeof(m_aErrorDetail));
|
mem_zero(m_aErrorDetail, sizeof(m_aErrorDetail));
|
||||||
mem_zero(&m_Mysql, sizeof(m_Mysql));
|
mem_zero(&m_Mysql, sizeof(m_Mysql));
|
||||||
mysql_init(&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));
|
|
||||||
str_copy(m_aBindaddr, pBindaddr, sizeof(m_aBindaddr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CMysqlConnection::~CMysqlConnection()
|
CMysqlConnection::~CMysqlConnection()
|
||||||
|
@ -215,13 +192,13 @@ void CMysqlConnection::Print(IConsole *pConsole, const char *pMode)
|
||||||
char aBuf[512];
|
char aBuf[512];
|
||||||
str_format(aBuf, sizeof(aBuf),
|
str_format(aBuf, sizeof(aBuf),
|
||||||
"MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
|
"MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
|
||||||
pMode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port);
|
pMode, m_Config.m_aDatabase, GetPrefix(), m_Config.m_aUser, m_Config.m_aIp, m_Config.m_Port);
|
||||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
CMysqlConnection *CMysqlConnection::Copy()
|
CMysqlConnection *CMysqlConnection::Copy()
|
||||||
{
|
{
|
||||||
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_aBindaddr, m_Port, m_Setup);
|
return new CMysqlConnection(m_Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
|
void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
|
||||||
|
@ -255,7 +232,7 @@ bool CMysqlConnection::ConnectImpl()
|
||||||
StoreErrorStmt("free_result");
|
StoreErrorStmt("free_result");
|
||||||
dbg_msg("mysql", "can't free last result %s", m_aErrorDetail);
|
dbg_msg("mysql", "can't free last result %s", m_aErrorDetail);
|
||||||
}
|
}
|
||||||
if(!mysql_select_db(&m_Mysql, m_aDatabase))
|
if(!mysql_select_db(&m_Mysql, m_Config.m_aDatabase))
|
||||||
{
|
{
|
||||||
// Success.
|
// Success.
|
||||||
return false;
|
return false;
|
||||||
|
@ -277,12 +254,12 @@ bool CMysqlConnection::ConnectImpl()
|
||||||
mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout);
|
mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout);
|
||||||
mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect);
|
mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect);
|
||||||
mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
|
mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
|
||||||
if(m_aBindaddr[0] != '\0')
|
if(m_Config.m_aBindaddr[0] != '\0')
|
||||||
{
|
{
|
||||||
mysql_options(&m_Mysql, MYSQL_OPT_BIND, m_aBindaddr);
|
mysql_options(&m_Mysql, MYSQL_OPT_BIND, m_Config.m_aBindaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mysql_real_connect(&m_Mysql, m_aIp, m_aUser, m_aPass, nullptr, m_Port, nullptr, CLIENT_IGNORE_SIGPIPE))
|
if(!mysql_real_connect(&m_Mysql, m_Config.m_aIp, m_Config.m_aUser, m_Config.m_aPass, nullptr, m_Config.m_Port, nullptr, CLIENT_IGNORE_SIGPIPE))
|
||||||
{
|
{
|
||||||
StoreErrorMysql("real_connect");
|
StoreErrorMysql("real_connect");
|
||||||
return true;
|
return true;
|
||||||
|
@ -297,11 +274,11 @@ bool CMysqlConnection::ConnectImpl()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_Setup)
|
if(m_Config.m_Setup)
|
||||||
{
|
{
|
||||||
char aCreateDatabase[1024];
|
char aCreateDatabase[1024];
|
||||||
// create database
|
// create database
|
||||||
str_format(aCreateDatabase, sizeof(aCreateDatabase), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
|
str_format(aCreateDatabase, sizeof(aCreateDatabase), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_Config.m_aDatabase);
|
||||||
if(PrepareAndExecuteStatement(aCreateDatabase))
|
if(PrepareAndExecuteStatement(aCreateDatabase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -309,23 +286,23 @@ bool CMysqlConnection::ConnectImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to specific database
|
// Connect to specific database
|
||||||
if(mysql_select_db(&m_Mysql, m_aDatabase))
|
if(mysql_select_db(&m_Mysql, m_Config.m_aDatabase))
|
||||||
{
|
{
|
||||||
StoreErrorMysql("select_db");
|
StoreErrorMysql("select_db");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_Setup)
|
if(m_Config.m_Setup)
|
||||||
{
|
{
|
||||||
char aCreateRace[1024];
|
char aCreateRace[1024];
|
||||||
char aCreateTeamrace[1024];
|
char aCreateTeamrace[1024];
|
||||||
char aCreateMaps[1024];
|
char aCreateMaps[1024];
|
||||||
char aCreateSaves[1024];
|
char aCreateSaves[1024];
|
||||||
char aCreatePoints[1024];
|
char aCreatePoints[1024];
|
||||||
FormatCreateRace(aCreateRace, sizeof(aCreateRace));
|
FormatCreateRace(aCreateRace, sizeof(aCreateRace), /* Backup */ false);
|
||||||
FormatCreateTeamrace(aCreateTeamrace, sizeof(aCreateTeamrace), "VARBINARY(16)");
|
FormatCreateTeamrace(aCreateTeamrace, sizeof(aCreateTeamrace), "VARBINARY(16)", /* Backup */ false);
|
||||||
FormatCreateMaps(aCreateMaps, sizeof(aCreateMaps));
|
FormatCreateMaps(aCreateMaps, sizeof(aCreateMaps));
|
||||||
FormatCreateSaves(aCreateSaves, sizeof(aCreateSaves));
|
FormatCreateSaves(aCreateSaves, sizeof(aCreateSaves), /* Backup */ false);
|
||||||
FormatCreatePoints(aCreatePoints, sizeof(aCreatePoints));
|
FormatCreatePoints(aCreatePoints, sizeof(aCreatePoints));
|
||||||
|
|
||||||
if(PrepareAndExecuteStatement(aCreateRace) ||
|
if(PrepareAndExecuteStatement(aCreateRace) ||
|
||||||
|
@ -336,7 +313,7 @@ bool CMysqlConnection::ConnectImpl()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
m_Setup = false;
|
m_Config.m_Setup = false;
|
||||||
}
|
}
|
||||||
dbg_msg("mysql", "connection established");
|
dbg_msg("mysql", "connection established");
|
||||||
return false;
|
return false;
|
||||||
|
@ -713,19 +690,15 @@ bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError,
|
||||||
return ExecuteUpdate(&NumUpdated, pError, ErrorSize);
|
return ExecuteUpdate(&NumUpdated, pError, ErrorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config)
|
||||||
const char *pDatabase,
|
|
||||||
const char *pPrefix,
|
|
||||||
const char *pUser,
|
|
||||||
const char *pPass,
|
|
||||||
const char *pIp,
|
|
||||||
const char *pBindaddr,
|
|
||||||
int Port,
|
|
||||||
bool Setup)
|
|
||||||
{
|
{
|
||||||
return std::make_unique<CMysqlConnection>(pDatabase, pPrefix, pUser, pPass, pIp, pBindaddr, Port, Setup);
|
return std::make_unique<CMysqlConnection>(Config);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
bool MysqlAvailable()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
int MysqlInit()
|
int MysqlInit()
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -733,15 +706,7 @@ int MysqlInit()
|
||||||
void MysqlUninit()
|
void MysqlUninit()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(CMysqlConfig Config)
|
||||||
const char *pDatabase,
|
|
||||||
const char *pPrefix,
|
|
||||||
const char *pUser,
|
|
||||||
const char *pPass,
|
|
||||||
const char *pIp,
|
|
||||||
const char *pBindaddr,
|
|
||||||
int Port,
|
|
||||||
bool Setup)
|
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ public:
|
||||||
|
|
||||||
bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
|
bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
|
||||||
|
|
||||||
|
// fail safe
|
||||||
|
bool CreateFailsafeTables();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// copy of config vars
|
// copy of config vars
|
||||||
char m_aFilename[IO_MAX_PATH_LENGTH];
|
char m_aFilename[IO_MAX_PATH_LENGTH];
|
||||||
|
@ -139,20 +142,32 @@ bool CSqliteConnection::Connect(char *pError, int ErrorSize)
|
||||||
|
|
||||||
if(m_Setup)
|
if(m_Setup)
|
||||||
{
|
{
|
||||||
|
if(Execute("PRAGMA journal_mode=WAL", pError, ErrorSize))
|
||||||
|
return true;
|
||||||
char aBuf[1024];
|
char aBuf[1024];
|
||||||
FormatCreateRace(aBuf, sizeof(aBuf));
|
FormatCreateRace(aBuf, sizeof(aBuf), /* Backup */ false);
|
||||||
if(Execute(aBuf, pError, ErrorSize))
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
return true;
|
return true;
|
||||||
FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB");
|
FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB", /* Backup */ false);
|
||||||
if(Execute(aBuf, pError, ErrorSize))
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
return true;
|
return true;
|
||||||
FormatCreateMaps(aBuf, sizeof(aBuf));
|
FormatCreateMaps(aBuf, sizeof(aBuf));
|
||||||
if(Execute(aBuf, pError, ErrorSize))
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
return true;
|
return true;
|
||||||
FormatCreateSaves(aBuf, sizeof(aBuf));
|
FormatCreateSaves(aBuf, sizeof(aBuf), /* Backup */ false);
|
||||||
if(Execute(aBuf, pError, ErrorSize))
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
return true;
|
return true;
|
||||||
FormatCreatePoints(aBuf, sizeof(aBuf));
|
FormatCreatePoints(aBuf, sizeof(aBuf));
|
||||||
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
FormatCreateRace(aBuf, sizeof(aBuf), /* Backup */ true);
|
||||||
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
|
return true;
|
||||||
|
FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB", /* Backup */ true);
|
||||||
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
|
return true;
|
||||||
|
FormatCreateSaves(aBuf, sizeof(aBuf), /* Backup */ true);
|
||||||
if(Execute(aBuf, pError, ErrorSize))
|
if(Execute(aBuf, pError, ErrorSize))
|
||||||
return true;
|
return true;
|
||||||
m_Setup = false;
|
m_Setup = false;
|
||||||
|
|
|
@ -2543,17 +2543,15 @@ int CServer::Run()
|
||||||
{
|
{
|
||||||
char aFullPath[IO_MAX_PATH_LENGTH];
|
char aFullPath[IO_MAX_PATH_LENGTH];
|
||||||
Storage()->GetCompletePath(IStorage::TYPE_SAVE_OR_ABSOLUTE, Config()->m_SvSqliteFile, aFullPath, sizeof(aFullPath));
|
Storage()->GetCompletePath(IStorage::TYPE_SAVE_OR_ABSOLUTE, Config()->m_SvSqliteFile, aFullPath, sizeof(aFullPath));
|
||||||
auto pSqliteConn = CreateSqliteConnection(aFullPath, true);
|
|
||||||
|
|
||||||
if(Config()->m_SvUseSQL)
|
if(Config()->m_SvUseSQL)
|
||||||
{
|
{
|
||||||
DbPool()->RegisterDatabase(std::move(pSqliteConn), CDbConnectionPool::WRITE_BACKUP);
|
DbPool()->RegisterSqliteDatabase(CDbConnectionPool::WRITE_BACKUP, Config()->m_SvSqliteFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto pCopy = std::unique_ptr<IDbConnection>(pSqliteConn->Copy());
|
DbPool()->RegisterSqliteDatabase(CDbConnectionPool::READ, Config()->m_SvSqliteFile);
|
||||||
DbPool()->RegisterDatabase(std::move(pSqliteConn), CDbConnectionPool::READ);
|
DbPool()->RegisterSqliteDatabase(CDbConnectionPool::WRITE, Config()->m_SvSqliteFile);
|
||||||
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3377,6 +3375,12 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
|
||||||
{
|
{
|
||||||
CServer *pSelf = (CServer *)pUserData;
|
CServer *pSelf = (CServer *)pUserData;
|
||||||
|
|
||||||
|
if(!MysqlAvailable())
|
||||||
|
{
|
||||||
|
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!pSelf->Config()->m_SvUseSQL)
|
if(!pSelf->Config()->m_SvUseSQL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -3386,38 +3390,34 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadOnly;
|
CMysqlConfig Config;
|
||||||
|
bool Write;
|
||||||
if(str_comp_nocase(pResult->GetString(0), "w") == 0)
|
if(str_comp_nocase(pResult->GetString(0), "w") == 0)
|
||||||
ReadOnly = false;
|
Write = false;
|
||||||
else if(str_comp_nocase(pResult->GetString(0), "r") == 0)
|
else if(str_comp_nocase(pResult->GetString(0), "r") == 0)
|
||||||
ReadOnly = true;
|
Write = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer");
|
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
|
str_copy(Config.m_aDatabase, pResult->GetString(1), sizeof(Config.m_aDatabase));
|
||||||
|
str_copy(Config.m_aPrefix, pResult->GetString(2), sizeof(Config.m_aPrefix));
|
||||||
auto pMysqlConn = CreateMysqlConnection(
|
str_copy(Config.m_aUser, pResult->GetString(3), sizeof(Config.m_aUser));
|
||||||
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
str_copy(Config.m_aPass, pResult->GetString(4), sizeof(Config.m_aPass));
|
||||||
pResult->GetString(4), pResult->GetString(5), g_Config.m_SvSqlBindaddr,
|
str_copy(Config.m_aIp, pResult->GetString(5), sizeof(Config.m_aIp));
|
||||||
pResult->GetInteger(6), SetUpDb);
|
str_copy(Config.m_aBindaddr, Config.m_aBindaddr, sizeof(Config.m_aBindaddr));
|
||||||
|
Config.m_Port = pResult->GetInteger(6);
|
||||||
if(!pMysqlConn)
|
Config.m_Setup = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
|
||||||
{
|
|
||||||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char aBuf[512];
|
char aBuf[512];
|
||||||
str_format(aBuf, sizeof(aBuf),
|
str_format(aBuf, sizeof(aBuf),
|
||||||
"Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d",
|
"Adding new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d",
|
||||||
ReadOnly ? "Read" : "Write",
|
Write ? "Write" : "Read",
|
||||||
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
Config.m_aDatabase, Config.m_aPrefix, Config.m_aUser, Config.m_aIp, Config.m_Port);
|
||||||
pResult->GetString(5), pResult->GetInteger(6));
|
|
||||||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
||||||
pSelf->DbPool()->RegisterDatabase(std::move(pMysqlConn), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE);
|
pSelf->DbPool()->RegisterMysqlDatabase(Write ? CDbConnectionPool::WRITE : CDbConnectionPool::READ, &Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
|
void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
|
||||||
|
|
|
@ -173,6 +173,7 @@ void CScore::SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const
|
||||||
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));
|
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));
|
||||||
FormatUuid(GameServer()->GameUuid(), Tmp->m_aGameUuid, sizeof(Tmp->m_aGameUuid));
|
FormatUuid(GameServer()->GameUuid(), Tmp->m_aGameUuid, sizeof(Tmp->m_aGameUuid));
|
||||||
str_copy(Tmp->m_aMap, g_Config.m_SvMap, sizeof(Tmp->m_aMap));
|
str_copy(Tmp->m_aMap, g_Config.m_SvMap, sizeof(Tmp->m_aMap));
|
||||||
|
Tmp->m_TeamrankUuid = RandomUuid();
|
||||||
|
|
||||||
m_pPool->ExecuteWrite(CScoreWorker::SaveTeamScore, std::move(Tmp), "save team score");
|
m_pPool->ExecuteWrite(CScoreWorker::SaveTeamScore, std::move(Tmp), "save team score");
|
||||||
}
|
}
|
||||||
|
@ -292,7 +293,24 @@ void CScore::SaveTeam(int ClientID, const char *pCode, const char *pServer)
|
||||||
Tmp->m_aGeneratedCode[0] = '\0';
|
Tmp->m_aGeneratedCode[0] = '\0';
|
||||||
GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode));
|
GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode));
|
||||||
|
|
||||||
|
char aBuf[512];
|
||||||
|
if(Tmp->m_aCode[0] == '\0')
|
||||||
|
{
|
||||||
|
str_format(aBuf,
|
||||||
|
sizeof(aBuf),
|
||||||
|
"Team save in progress. You'll be able to load with '/load %s'",
|
||||||
|
Tmp->m_aGeneratedCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str_format(aBuf,
|
||||||
|
sizeof(aBuf),
|
||||||
|
"Team save in progress. You'll be able to load with '/load %s' if save is successful or with '/load %s' if it fails",
|
||||||
|
Tmp->m_aCode,
|
||||||
|
Tmp->m_aGeneratedCode);
|
||||||
|
}
|
||||||
pController->m_Teams.KillSavedTeam(ClientID, Team);
|
pController->m_Teams.KillSavedTeam(ClientID, Team);
|
||||||
|
GameServer()->SendChatTeam(Team, aBuf);
|
||||||
m_pPool->ExecuteWrite(CScoreWorker::SaveTeam, std::move(Tmp), "save team");
|
m_pPool->ExecuteWrite(CScoreWorker::SaveTeam, std::move(Tmp), "save team");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -392,7 +392,7 @@ bool CScoreWorker::MapInfo(IDbConnection *pSqlServer, const ISqlData *pGameData,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize)
|
bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize)
|
||||||
{
|
{
|
||||||
const CSqlScoreData *pData = dynamic_cast<const CSqlScoreData *>(pGameData);
|
const CSqlScoreData *pData = dynamic_cast<const CSqlScoreData *>(pGameData);
|
||||||
CScorePlayerResult *pResult = dynamic_cast<CScorePlayerResult *>(pGameData->m_pResult.get());
|
CScorePlayerResult *pResult = dynamic_cast<CScorePlayerResult *>(pGameData->m_pResult.get());
|
||||||
|
@ -400,52 +400,105 @@ bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameDat
|
||||||
|
|
||||||
char aBuf[1024];
|
char aBuf[1024];
|
||||||
|
|
||||||
str_format(aBuf, sizeof(aBuf),
|
if(w == Write::NORMAL_SUCCEEDED)
|
||||||
"SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map=? AND Name=? ORDER BY time ASC LIMIT 1",
|
|
||||||
pSqlServer->GetPrefix());
|
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
|
||||||
{
|
{
|
||||||
return true;
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"DELETE FROM %s_race_backup WHERE GameId=? AND Name=? AND Timestamp=%s",
|
||||||
|
pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGameUuid);
|
||||||
|
pSqlServer->BindString(2, pData->m_aName);
|
||||||
|
pSqlServer->BindString(3, pData->m_aTimestamp);
|
||||||
|
pSqlServer->Print();
|
||||||
|
int NumInserted;
|
||||||
|
pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
pSqlServer->BindString(1, pData->m_aMap);
|
if(w == Write::NORMAL_FAILED)
|
||||||
pSqlServer->BindString(2, pData->m_aName);
|
{
|
||||||
|
int NumInserted;
|
||||||
|
// move to non-tmp table succeded. delete from backup again
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"INSERT INTO %s_race SELECT * FROM %s_race_backup WHERE GameId=? AND Name=? AND Timestamp=%s",
|
||||||
|
pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGameUuid);
|
||||||
|
pSqlServer->BindString(2, pData->m_aName);
|
||||||
|
pSqlServer->BindString(3, pData->m_aTimestamp);
|
||||||
|
pSqlServer->Print();
|
||||||
|
pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
|
|
||||||
bool End;
|
// move to non-tmp table succeded. delete from backup again
|
||||||
if(pSqlServer->Step(&End, pError, ErrorSize))
|
str_format(aBuf, sizeof(aBuf),
|
||||||
{
|
"DELETE FROM %s_race_backup WHERE GameId=? AND Name=? AND Timestamp=%s",
|
||||||
return true;
|
pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGameUuid);
|
||||||
|
pSqlServer->BindString(2, pData->m_aName);
|
||||||
|
pSqlServer->BindString(3, pData->m_aTimestamp);
|
||||||
|
pSqlServer->Print();
|
||||||
|
pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
int NumFinished = pSqlServer->GetInt(1);
|
|
||||||
if(NumFinished == 0)
|
if(w == Write::NORMAL)
|
||||||
{
|
{
|
||||||
str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map=?", pSqlServer->GetPrefix());
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map=? AND Name=? ORDER BY time ASC LIMIT 1",
|
||||||
|
pSqlServer->GetPrefix());
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pSqlServer->BindString(1, pData->m_aMap);
|
pSqlServer->BindString(1, pData->m_aMap);
|
||||||
|
pSqlServer->BindString(2, pData->m_aName);
|
||||||
|
|
||||||
bool End2;
|
bool End;
|
||||||
if(pSqlServer->Step(&End2, pError, ErrorSize))
|
if(pSqlServer->Step(&End, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(!End2)
|
int NumFinished = pSqlServer->GetInt(1);
|
||||||
|
if(NumFinished == 0)
|
||||||
{
|
{
|
||||||
int Points = pSqlServer->GetInt(1);
|
str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map=?", pSqlServer->GetPrefix());
|
||||||
if(pSqlServer->AddPoints(pData->m_aName, Points, pError, ErrorSize))
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
str_format(paMessages[0], sizeof(paMessages[0]),
|
pSqlServer->BindString(1, pData->m_aMap);
|
||||||
"You earned %d point%s for finishing this map!",
|
|
||||||
Points, Points == 1 ? "" : "s");
|
bool End2;
|
||||||
|
if(pSqlServer->Step(&End2, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!End2)
|
||||||
|
{
|
||||||
|
int Points = pSqlServer->GetInt(1);
|
||||||
|
if(pSqlServer->AddPoints(pData->m_aName, Points, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
str_format(paMessages[0], sizeof(paMessages[0]),
|
||||||
|
"You earned %d point%s for finishing this map!",
|
||||||
|
Points, Points == 1 ? "" : "s");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save score. Can't fail, because no UNIQUE/PRIMARY KEY constrain is defined.
|
// save score. Can't fail, because no UNIQUE/PRIMARY KEY constrain is defined.
|
||||||
str_format(aBuf, sizeof(aBuf),
|
str_format(aBuf, sizeof(aBuf),
|
||||||
"%s INTO %s_race("
|
"%s INTO %s_race%s("
|
||||||
" Map, Name, Timestamp, Time, Server, "
|
" Map, Name, Timestamp, Time, Server, "
|
||||||
" cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
|
" cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
|
||||||
" cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
|
" cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
|
||||||
|
@ -456,6 +509,7 @@ bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameDat
|
||||||
" %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
|
" %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
|
||||||
" ?, %s)",
|
" ?, %s)",
|
||||||
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(),
|
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(),
|
||||||
|
w == Write::NORMAL ? "" : "_backup",
|
||||||
pSqlServer->InsertTimestampAsUtc(), pData->m_Time,
|
pSqlServer->InsertTimestampAsUtc(), pData->m_Time,
|
||||||
pData->m_aCurrentTimeCp[0], pData->m_aCurrentTimeCp[1], pData->m_aCurrentTimeCp[2],
|
pData->m_aCurrentTimeCp[0], pData->m_aCurrentTimeCp[1], pData->m_aCurrentTimeCp[2],
|
||||||
pData->m_aCurrentTimeCp[3], pData->m_aCurrentTimeCp[4], pData->m_aCurrentTimeCp[5],
|
pData->m_aCurrentTimeCp[3], pData->m_aCurrentTimeCp[4], pData->m_aCurrentTimeCp[5],
|
||||||
|
@ -480,108 +534,159 @@ bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameDat
|
||||||
return pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
return pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CScoreWorker::SaveTeamScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize)
|
bool CScoreWorker::SaveTeamScore(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize)
|
||||||
{
|
{
|
||||||
const CSqlTeamScoreData *pData = dynamic_cast<const CSqlTeamScoreData *>(pGameData);
|
const CSqlTeamScoreData *pData = dynamic_cast<const CSqlTeamScoreData *>(pGameData);
|
||||||
|
|
||||||
char aBuf[512];
|
char aBuf[512];
|
||||||
|
|
||||||
// get the names sorted in a tab separated string
|
if(w == Write::NORMAL_SUCCEEDED)
|
||||||
std::vector<std::string> vNames;
|
|
||||||
for(unsigned int i = 0; i < pData->m_Size; i++)
|
|
||||||
vNames.emplace_back(pData->m_aaNames[i]);
|
|
||||||
|
|
||||||
std::sort(vNames.begin(), vNames.end());
|
|
||||||
str_format(aBuf, sizeof(aBuf),
|
|
||||||
"SELECT l.ID, Name, Time "
|
|
||||||
"FROM (" // preselect teams with first name in team
|
|
||||||
" SELECT ID "
|
|
||||||
" FROM %s_teamrace "
|
|
||||||
" WHERE Map = ? AND Name = ? AND DDNet7 = %s"
|
|
||||||
") as l INNER JOIN %s_teamrace AS r ON l.ID = r.ID "
|
|
||||||
"ORDER BY l.ID, Name COLLATE %s",
|
|
||||||
pSqlServer->GetPrefix(), pSqlServer->False(), pSqlServer->GetPrefix(), pSqlServer->BinaryCollate());
|
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
|
||||||
{
|
{
|
||||||
return true;
|
str_format(aBuf, sizeof(aBuf),
|
||||||
}
|
"DELETE FROM %s_teamrace_backup WHERE GameId=?",
|
||||||
pSqlServer->BindString(1, pData->m_aMap);
|
pSqlServer->GetPrefix());
|
||||||
pSqlServer->BindString(2, pData->m_aaNames[0]);
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
|
||||||
bool FoundTeam = false;
|
|
||||||
float Time;
|
|
||||||
CTeamrank Teamrank;
|
|
||||||
bool End;
|
|
||||||
if(pSqlServer->Step(&End, pError, ErrorSize))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if(!End)
|
|
||||||
{
|
|
||||||
bool SearchTeamEnd = false;
|
|
||||||
while(!SearchTeamEnd)
|
|
||||||
{
|
{
|
||||||
Time = pSqlServer->GetFloat(3);
|
return true;
|
||||||
if(Teamrank.NextSqlResult(pSqlServer, &SearchTeamEnd, pError, ErrorSize))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if(Teamrank.SamePlayers(&vNames))
|
|
||||||
{
|
|
||||||
FoundTeam = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy uuid, because mysql BindBlob doesn't support const buffers
|
||||||
|
CUuid TeamrankId = pData->m_TeamrankUuid;
|
||||||
|
pSqlServer->BindBlob(1, TeamrankId.m_aData, sizeof(TeamrankId.m_aData));
|
||||||
|
int NumInserted;
|
||||||
|
return pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
}
|
}
|
||||||
if(FoundTeam)
|
if(w == Write::NORMAL_FAILED)
|
||||||
{
|
{
|
||||||
dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time);
|
int NumInserted;
|
||||||
if(pData->m_Time < Time)
|
CUuid TeamrankId = pData->m_TeamrankUuid;
|
||||||
|
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"INSERT INTO %s_teamrace SELECT * FROM %s_teamrace_backup WHERE GameId=?",
|
||||||
|
pSqlServer->GetPrefix(), pSqlServer->GetPrefix());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
str_format(aBuf, sizeof(aBuf),
|
return true;
|
||||||
"UPDATE %s_teamrace SET Time=%.2f, Timestamp=?, DDNet7=%s, GameID=? WHERE ID = ?",
|
|
||||||
pSqlServer->GetPrefix(), pData->m_Time, pSqlServer->False());
|
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pSqlServer->BindString(1, pData->m_aTimestamp);
|
|
||||||
pSqlServer->BindString(2, pData->m_aGameUuid);
|
|
||||||
pSqlServer->BindBlob(3, Teamrank.m_TeamID.m_aData, sizeof(Teamrank.m_TeamID.m_aData));
|
|
||||||
pSqlServer->Print();
|
|
||||||
int NumUpdated;
|
|
||||||
if(pSqlServer->ExecuteUpdate(&NumUpdated, pError, ErrorSize))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pSqlServer->BindBlob(1, TeamrankId.m_aData, sizeof(TeamrankId.m_aData));
|
||||||
|
if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"DELETE FROM %s_teamrace_backup WHERE GameId=?",
|
||||||
|
pSqlServer->GetPrefix());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindBlob(1, TeamrankId.m_aData, sizeof(TeamrankId.m_aData));
|
||||||
|
return pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if(w == Write::NORMAL)
|
||||||
{
|
{
|
||||||
CUuid GameID = RandomUuid();
|
// get the names sorted in a tab separated string
|
||||||
|
std::vector<std::string> vNames;
|
||||||
for(unsigned int i = 0; i < pData->m_Size; i++)
|
for(unsigned int i = 0; i < pData->m_Size; i++)
|
||||||
|
vNames.emplace_back(pData->m_aaNames[i]);
|
||||||
|
|
||||||
|
std::sort(vNames.begin(), vNames.end());
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"SELECT l.ID, Name, Time "
|
||||||
|
"FROM (" // preselect teams with first name in team
|
||||||
|
" SELECT ID "
|
||||||
|
" FROM %s_teamrace "
|
||||||
|
" WHERE Map = ? AND Name = ? AND DDNet7 = %s"
|
||||||
|
") as l INNER JOIN %s_teamrace AS r ON l.ID = r.ID "
|
||||||
|
"ORDER BY l.ID, Name COLLATE %s",
|
||||||
|
pSqlServer->GetPrefix(), pSqlServer->False(), pSqlServer->GetPrefix(), pSqlServer->BinaryCollate());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
// if no entry found... create a new one
|
return true;
|
||||||
str_format(aBuf, sizeof(aBuf),
|
}
|
||||||
"%s INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) "
|
pSqlServer->BindString(1, pData->m_aMap);
|
||||||
"VALUES (?, ?, %s, %.2f, ?, ?, %s)",
|
pSqlServer->BindString(2, pData->m_aaNames[0]);
|
||||||
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(),
|
|
||||||
pSqlServer->InsertTimestampAsUtc(), pData->m_Time, pSqlServer->False());
|
bool FoundTeam = false;
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
float Time;
|
||||||
|
CTeamrank Teamrank;
|
||||||
|
bool End;
|
||||||
|
if(pSqlServer->Step(&End, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!End)
|
||||||
|
{
|
||||||
|
bool SearchTeamEnd = false;
|
||||||
|
while(!SearchTeamEnd)
|
||||||
{
|
{
|
||||||
return true;
|
Time = pSqlServer->GetFloat(3);
|
||||||
|
if(Teamrank.NextSqlResult(pSqlServer, &SearchTeamEnd, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(Teamrank.SamePlayers(&vNames))
|
||||||
|
{
|
||||||
|
FoundTeam = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pSqlServer->BindString(1, pData->m_aMap);
|
}
|
||||||
pSqlServer->BindString(2, pData->m_aaNames[i]);
|
if(FoundTeam)
|
||||||
pSqlServer->BindString(3, pData->m_aTimestamp);
|
{
|
||||||
pSqlServer->BindBlob(4, GameID.m_aData, sizeof(GameID.m_aData));
|
dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time);
|
||||||
pSqlServer->BindString(5, pData->m_aGameUuid);
|
if(pData->m_Time < Time)
|
||||||
pSqlServer->Print();
|
|
||||||
int NumInserted;
|
|
||||||
if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize))
|
|
||||||
{
|
{
|
||||||
return true;
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"UPDATE %s_teamrace SET Time=%.2f, Timestamp=?, DDNet7=%s, GameID=? WHERE ID = ?",
|
||||||
|
pSqlServer->GetPrefix(), pData->m_Time, pSqlServer->False());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aTimestamp);
|
||||||
|
pSqlServer->BindString(2, pData->m_aGameUuid);
|
||||||
|
// copy uuid, because mysql BindBlob doesn't support const buffers
|
||||||
|
CUuid TeamrankId = pData->m_TeamrankUuid;
|
||||||
|
pSqlServer->BindBlob(3, TeamrankId.m_aData, sizeof(TeamrankId.m_aData));
|
||||||
|
pSqlServer->Print();
|
||||||
|
int NumUpdated;
|
||||||
|
if(pSqlServer->ExecuteUpdate(&NumUpdated, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < pData->m_Size; i++)
|
||||||
|
{
|
||||||
|
// if no entry found... create a new one
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"%s INTO %s_teamrace%s(Map, Name, Timestamp, Time, ID, GameID, DDNet7) "
|
||||||
|
"VALUES (?, ?, %s, %.2f, ?, ?, %s)",
|
||||||
|
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(),
|
||||||
|
w == Write::NORMAL ? "" : "_backup",
|
||||||
|
pSqlServer->InsertTimestampAsUtc(), pData->m_Time, pSqlServer->False());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aMap);
|
||||||
|
pSqlServer->BindString(2, pData->m_aaNames[i]);
|
||||||
|
pSqlServer->BindString(3, pData->m_aTimestamp);
|
||||||
|
// copy uuid, because mysql BindBlob doesn't support const buffers
|
||||||
|
CUuid TeamrankId = pData->m_TeamrankUuid;
|
||||||
|
pSqlServer->BindBlob(4, TeamrankId.m_aData, sizeof(TeamrankId.m_aData));
|
||||||
|
pSqlServer->BindString(5, pData->m_aGameUuid);
|
||||||
|
pSqlServer->Print();
|
||||||
|
int NumInserted;
|
||||||
|
if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1363,19 +1468,64 @@ bool CScoreWorker::RandomUnfinishedMap(IDbConnection *pSqlServer, const ISqlData
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize)
|
bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize)
|
||||||
{
|
{
|
||||||
const CSqlTeamSave *pData = dynamic_cast<const CSqlTeamSave *>(pGameData);
|
const CSqlTeamSave *pData = dynamic_cast<const CSqlTeamSave *>(pGameData);
|
||||||
CScoreSaveResult *pResult = dynamic_cast<CScoreSaveResult *>(pGameData->m_pResult.get());
|
CScoreSaveResult *pResult = dynamic_cast<CScoreSaveResult *>(pGameData->m_pResult.get());
|
||||||
|
|
||||||
|
if(w == Write::NORMAL_SUCCEEDED)
|
||||||
|
{
|
||||||
|
// write succeded on mysql server. delete from sqlite again
|
||||||
|
char aBuf[128] = {0};
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"DELETE FROM %s_saves_backup WHERE Code = ?",
|
||||||
|
pSqlServer->GetPrefix());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGeneratedCode);
|
||||||
|
bool End;
|
||||||
|
return pSqlServer->Step(&End, pError, ErrorSize);
|
||||||
|
}
|
||||||
|
if(w == Write::NORMAL_FAILED)
|
||||||
|
{
|
||||||
|
char aBuf[128] = {0};
|
||||||
|
bool End;
|
||||||
|
// move to non-tmp table succeded. delete from backup again
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"INSERT INTO %s_saves SELECT * FROM %s_saves_backup WHERE Code = ?",
|
||||||
|
pSqlServer->GetPrefix(), pSqlServer->GetPrefix());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGeneratedCode);
|
||||||
|
if(pSqlServer->Step(&End, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to non-tmp table succeded. delete from backup again
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"DELETE FROM %s_saves_backup WHERE Code = ?",
|
||||||
|
pSqlServer->GetPrefix());
|
||||||
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pSqlServer->BindString(1, pData->m_aGeneratedCode);
|
||||||
|
return pSqlServer->Step(&End, pError, ErrorSize);
|
||||||
|
}
|
||||||
|
|
||||||
char aSaveID[UUID_MAXSTRSIZE];
|
char aSaveID[UUID_MAXSTRSIZE];
|
||||||
FormatUuid(pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE);
|
FormatUuid(pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE);
|
||||||
|
|
||||||
char *pSaveState = pResult->m_SavedTeam.GetString();
|
char *pSaveState = pResult->m_SavedTeam.GetString();
|
||||||
char aBuf[65536];
|
char aBuf[65536];
|
||||||
|
|
||||||
dbg_msg("score/dbg", "code=%s failure=%d", pData->m_aCode, (int)Failure);
|
dbg_msg("score/dbg", "code=%s failure=%d", pData->m_aCode, (int)w);
|
||||||
bool UseGeneratedCode = pData->m_aCode[0] == '\0' || Failure;
|
bool UseGeneratedCode = pData->m_aCode[0] == '\0' || w != Write::NORMAL;
|
||||||
|
|
||||||
bool Retry = false;
|
bool Retry = false;
|
||||||
// two tries, first use the user provided code, then the autogenerated
|
// two tries, first use the user provided code, then the autogenerated
|
||||||
|
@ -1389,9 +1539,10 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData
|
||||||
str_copy(aCode, pData->m_aCode, sizeof(aCode));
|
str_copy(aCode, pData->m_aCode, sizeof(aCode));
|
||||||
|
|
||||||
str_format(aBuf, sizeof(aBuf),
|
str_format(aBuf, sizeof(aBuf),
|
||||||
"%s INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
|
"%s INTO %s_saves%s(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
|
||||||
"VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, %s)",
|
"VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, %s)",
|
||||||
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(), pSqlServer->False());
|
pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(),
|
||||||
|
w == Write::NORMAL ? "" : "_backup", pSqlServer->False());
|
||||||
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -1409,8 +1560,9 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData
|
||||||
}
|
}
|
||||||
if(NumInserted == 1)
|
if(NumInserted == 1)
|
||||||
{
|
{
|
||||||
if(!Failure)
|
if(w == Write::NORMAL)
|
||||||
{
|
{
|
||||||
|
pResult->m_aBroadcast[0] = '\0';
|
||||||
if(str_comp(pData->m_aServer, g_Config.m_SvSqlServerName) == 0)
|
if(str_comp(pData->m_aServer, g_Config.m_SvSqlServerName) == 0)
|
||||||
{
|
{
|
||||||
str_format(pResult->m_aMessage, sizeof(pResult->m_aMessage),
|
str_format(pResult->m_aMessage, sizeof(pResult->m_aMessage),
|
||||||
|
@ -1461,8 +1613,10 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CScoreWorker::LoadTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize)
|
bool CScoreWorker::LoadTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize)
|
||||||
{
|
{
|
||||||
|
if(w == Write::NORMAL_SUCCEEDED || Write::BACKUP_FIRST)
|
||||||
|
return false;
|
||||||
const CSqlTeamLoad *pData = dynamic_cast<const CSqlTeamLoad *>(pGameData);
|
const CSqlTeamLoad *pData = dynamic_cast<const CSqlTeamLoad *>(pGameData);
|
||||||
CScoreSaveResult *pResult = dynamic_cast<CScoreSaveResult *>(pGameData->m_pResult.get());
|
CScoreSaveResult *pResult = dynamic_cast<CScoreSaveResult *>(pGameData->m_pResult.get());
|
||||||
pResult->m_Status = CScoreSaveResult::LOAD_FAILED;
|
pResult->m_Status = CScoreSaveResult::LOAD_FAILED;
|
||||||
|
|
|
@ -185,6 +185,7 @@ struct CSqlTeamScoreData : ISqlData
|
||||||
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
||||||
unsigned int m_Size;
|
unsigned int m_Size;
|
||||||
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
||||||
|
CUuid m_TeamrankUuid;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CSqlTeamSave : ISqlData
|
struct CSqlTeamSave : ISqlData
|
||||||
|
@ -291,11 +292,11 @@ struct CScoreWorker
|
||||||
static bool ShowTopPoints(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
static bool ShowTopPoints(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
static bool GetSaves(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
static bool GetSaves(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
|
||||||
static bool SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
static bool SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize);
|
||||||
static bool LoadTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
static bool LoadTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize);
|
||||||
|
|
||||||
static bool SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
static bool SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize);
|
||||||
static bool SaveTeamScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
static bool SaveTeamScore(IDbConnection *pSqlServer, const ISqlData *pGameData, Write w, char *pError, int ErrorSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GAME_SERVER_SCOREWORKER_H
|
#endif // GAME_SERVER_SCOREWORKER_H
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "engine/server/databases/connection_pool.h"
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ struct Score : public testing::TestWithParam<IDbConnection *>
|
||||||
for(int i = 0; i < NUM_CHECKPOINTS; i++)
|
for(int i = 0; i < NUM_CHECKPOINTS; i++)
|
||||||
ScoreData.m_aCurrentTimeCp[i] = WithTimeCheckPoints ? i : 0;
|
ScoreData.m_aCurrentTimeCp[i] = WithTimeCheckPoints ? i : 0;
|
||||||
str_copy(ScoreData.m_aRequestingPlayer, "deen", sizeof(ScoreData.m_aRequestingPlayer));
|
str_copy(ScoreData.m_aRequestingPlayer, "deen", sizeof(ScoreData.m_aRequestingPlayer));
|
||||||
ASSERT_FALSE(CScoreWorker::SaveScore(m_pConn, &ScoreData, false, m_aError, sizeof(m_aError))) << m_aError;
|
ASSERT_FALSE(CScoreWorker::SaveScore(m_pConn, &ScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExpectLines(const std::shared_ptr<CScorePlayerResult> &pPlayerResult, std::initializer_list<const char *> Lines, bool All = false)
|
void ExpectLines(const std::shared_ptr<CScorePlayerResult> &pPlayerResult, std::initializer_list<const char *> Lines, bool All = false)
|
||||||
|
@ -248,7 +249,7 @@ struct TeamScore : public Score
|
||||||
str_copy(teamScoreData.m_aaNames[1], "brainless tee", sizeof(teamScoreData.m_aaNames[1]));
|
str_copy(teamScoreData.m_aaNames[1], "brainless tee", sizeof(teamScoreData.m_aaNames[1]));
|
||||||
teamScoreData.m_Time = 100.0;
|
teamScoreData.m_Time = 100.0;
|
||||||
str_copy(teamScoreData.m_aTimestamp, "2021-11-24 19:24:08", sizeof(teamScoreData.m_aTimestamp));
|
str_copy(teamScoreData.m_aTimestamp, "2021-11-24 19:24:08", sizeof(teamScoreData.m_aTimestamp));
|
||||||
ASSERT_FALSE(CScoreWorker::SaveTeamScore(m_pConn, &teamScoreData, false, m_aError, sizeof(m_aError))) << m_aError;
|
ASSERT_FALSE(CScoreWorker::SaveTeamScore(m_pConn, &teamScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
|
||||||
|
|
||||||
str_copy(m_PlayerRequest.m_aMap, "Kobra 3", sizeof(m_PlayerRequest.m_aMap));
|
str_copy(m_PlayerRequest.m_aMap, "Kobra 3", sizeof(m_PlayerRequest.m_aMap));
|
||||||
str_copy(m_PlayerRequest.m_aRequestingPlayer, "brainless tee", sizeof(m_PlayerRequest.m_aRequestingPlayer));
|
str_copy(m_PlayerRequest.m_aRequestingPlayer, "brainless tee", sizeof(m_PlayerRequest.m_aRequestingPlayer));
|
||||||
|
@ -515,7 +516,17 @@ TEST_P(RandomMap, UnfinishedDoesntExist)
|
||||||
|
|
||||||
auto g_pSqliteConn = CreateSqliteConnection(":memory:", true);
|
auto g_pSqliteConn = CreateSqliteConnection(":memory:", true);
|
||||||
#if defined(CONF_TEST_MYSQL)
|
#if defined(CONF_TEST_MYSQL)
|
||||||
auto g_pMysqlConn = CreateMysqlConnection("ddnet", "record", "ddnet", "thebestpassword", "localhost", "", 3306, true);
|
CMysqlConfig gMysqlConfig{
|
||||||
|
"ddnet", // database
|
||||||
|
"record", // prefix
|
||||||
|
"ddnet", // user
|
||||||
|
"thebestpassword", // password
|
||||||
|
"localhost", // ip
|
||||||
|
"", // bindaddr
|
||||||
|
3306, // port
|
||||||
|
true, // setup
|
||||||
|
};
|
||||||
|
auto g_pMysqlConn = CreateMysqlConnection(gMysqlConfig);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto g_TestValues
|
auto g_TestValues
|
||||||
|
|
Loading…
Reference in a new issue