2020-07-07 17:29:44 +00:00
|
|
|
#include "sqlite.h"
|
|
|
|
|
|
|
|
#include <base/math.h>
|
2020-08-03 14:18:22 +00:00
|
|
|
#include <engine/console.h>
|
2020-07-07 17:29:44 +00:00
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
|
|
|
|
CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) :
|
|
|
|
IDbConnection("record"),
|
|
|
|
m_Setup(Setup),
|
|
|
|
m_pDb(nullptr),
|
|
|
|
m_pStmt(nullptr),
|
|
|
|
m_Done(true),
|
2020-07-12 10:21:41 +00:00
|
|
|
m_Locked(false),
|
2020-07-07 17:29:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-08-03 14:18:22 +00:00
|
|
|
|
|
|
|
void CSqliteConnection::Print(IConsole *pConsole, const char *Mode)
|
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
|
|
|
"SQLite-%s: DB: '%s'",
|
|
|
|
Mode, m_aFilename);
|
|
|
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-18 18:49:34 +00:00
|
|
|
void CSqliteConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
|
|
|
|
{
|
|
|
|
str_format(aBuf, BufferSize, "strftime('%%s', %s)", pTimestamp);
|
|
|
|
}
|
|
|
|
|
2020-07-07 17:29:44 +00:00
|
|
|
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));
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-07 17:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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];
|
2020-07-09 17:02:28 +00:00
|
|
|
FormatCreateRace(aBuf, sizeof(aBuf));
|
2020-07-07 17:29:44 +00:00
|
|
|
if(!Execute(aBuf))
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-09 17:18:18 +00:00
|
|
|
FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB");
|
2020-07-07 17:29:44 +00:00
|
|
|
if(!Execute(aBuf))
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-09 17:02:28 +00:00
|
|
|
FormatCreateMaps(aBuf, sizeof(aBuf));
|
2020-07-07 17:29:44 +00:00
|
|
|
if(!Execute(aBuf))
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-09 17:02:28 +00:00
|
|
|
FormatCreateSaves(aBuf, sizeof(aBuf));
|
2020-07-07 17:29:44 +00:00
|
|
|
if(!Execute(aBuf))
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-09 17:02:28 +00:00
|
|
|
FormatCreatePoints(aBuf, sizeof(aBuf));
|
2020-07-07 17:29:44 +00:00
|
|
|
if(!Execute(aBuf))
|
2020-08-03 11:11:52 +00:00
|
|
|
return Status::FAILURE;
|
2020-07-07 17:29:44 +00:00
|
|
|
m_Setup = false;
|
|
|
|
}
|
2020-07-12 10:21:41 +00:00
|
|
|
m_Locked = false;
|
2020-07-07 17:29:44 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-07-12 10:21:41 +00:00
|
|
|
// locks the whole database read/write
|
|
|
|
Execute("BEGIN EXCLUSIVE TRANSACTION;");
|
|
|
|
m_Locked = true;
|
2020-07-07 17:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSqliteConnection::Unlock()
|
|
|
|
{
|
2020-07-12 10:21:41 +00:00
|
|
|
if(m_Locked)
|
|
|
|
{
|
|
|
|
Execute("COMMIT TRANSACTION;");
|
|
|
|
m_Locked = false;
|
|
|
|
}
|
2020-07-07 17:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-14 22:12:08 +00:00
|
|
|
void CSqliteConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
|
|
|
|
{
|
|
|
|
int Result = sqlite3_bind_blob(m_pStmt, Idx, pBlob, Size, NULL);
|
|
|
|
ExceptionOnError(Result);
|
|
|
|
m_Done = false;
|
|
|
|
}
|
|
|
|
|
2020-07-07 17:29:44 +00:00
|
|
|
void CSqliteConnection::BindInt(int Idx, int Value)
|
|
|
|
{
|
|
|
|
int Result = sqlite3_bind_int(m_pStmt, Idx, Value);
|
|
|
|
ExceptionOnError(Result);
|
|
|
|
m_Done = false;
|
|
|
|
}
|
|
|
|
|
2020-07-14 22:12:08 +00:00
|
|
|
void CSqliteConnection::BindFloat(int Idx, float Value)
|
|
|
|
{
|
|
|
|
int Result = sqlite3_bind_double(m_pStmt, Idx, (double)Value);
|
|
|
|
ExceptionOnError(Result);
|
|
|
|
m_Done = false;
|
|
|
|
}
|
|
|
|
|
2020-07-07 17:29:44 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
2020-07-12 12:07:12 +00:00
|
|
|
|
|
|
|
void CSqliteConnection::AddPoints(const char *pPlayer, int Points)
|
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
|
|
|
"INSERT INTO %s_points(Name, Points) "
|
|
|
|
"VALUES (?, ?) "
|
|
|
|
"ON CONFLICT(Name) UPDATE SET Points=Points+?;",
|
|
|
|
GetPrefix());
|
|
|
|
PrepareStatement(aBuf);
|
|
|
|
BindString(1, pPlayer);
|
|
|
|
BindInt(2, Points);
|
|
|
|
BindInt(3, Points);
|
|
|
|
Step();
|
|
|
|
}
|