2 Commits

Author SHA1 Message Date
server
8bb5340909 db: use prepared statements for login lookups 2026-04-13 20:49:57 +02:00
server
6061e43c20 libsql: fix prepared statement result binding 2026-04-13 20:49:57 +02:00
5 changed files with 329 additions and 60 deletions

View File

@@ -7,12 +7,242 @@
#include "Config.h"
#include "QID.h"
#include "Cache.h"
#include "libsql/Statement.h"
extern std::string g_stLocale;
extern bool CreatePlayerTableFromRes(MYSQL_RES * res, TPlayerTable * pkTab);
extern int g_test_server;
extern int g_log;
namespace
{
bool PreparePlayerStmt(CStmt& stmt, const std::string& query)
{
CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER);
if (!sql)
{
sys_err("player SQL handle is not initialized");
return false;
}
return stmt.Prepare(sql, query.c_str());
}
bool LoadPlayerIndexTable(uint32_t accountId, TAccountTable* accountTable)
{
CStmt stmt;
const std::string query = std::string("SELECT pid1, pid2, pid3, pid4, empire FROM player_index") + GetTablePostfix() + " WHERE id=?";
uint32_t playerIds[PLAYER_PER_ACCOUNT] = {};
uint8_t empire = 0;
if (!PreparePlayerStmt(stmt, query))
return false;
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
return false;
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
{
if (!stmt.BindResult(MYSQL_TYPE_LONG, &playerIds[i]))
return false;
}
if (!stmt.BindResult(MYSQL_TYPE_TINY, &empire))
return false;
if (!stmt.Execute() || !stmt.Fetch())
return false;
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
accountTable->players[i].dwID = playerIds[i];
accountTable->bEmpire = empire;
return true;
}
bool CreatePlayerIndexRow(uint32_t accountId)
{
CStmt stmt;
const std::string query = std::string("INSERT INTO player_index") + GetTablePostfix() + " (id) VALUES(?)";
if (!PreparePlayerStmt(stmt, query))
return false;
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
return false;
return stmt.Execute();
}
void ApplyPlayerSummary(TAccountTable* accountTable, uint32_t playerId, const char* name, uint32_t job, uint32_t level, uint32_t progressValue,
int32_t st, int32_t ht, int32_t dx, int32_t iq, uint32_t mainPart, uint32_t hairPart, int32_t x, int32_t y, uint32_t skillGroup, uint32_t changeName)
{
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
{
if (accountTable->players[i].dwID != playerId)
continue;
CPlayerTableCache* cache = CClientManager::instance().GetPlayerCache(playerId);
TPlayerTable* playerTable = cache ? cache->Get(false) : NULL;
if (playerTable)
{
strlcpy(accountTable->players[i].szName, playerTable->name, sizeof(accountTable->players[i].szName));
accountTable->players[i].byJob = playerTable->job;
accountTable->players[i].byLevel = playerTable->level;
accountTable->players[i].dwPlayMinutes = playerTable->playtime;
accountTable->players[i].byST = playerTable->st;
accountTable->players[i].byHT = playerTable->ht;
accountTable->players[i].byDX = playerTable->dx;
accountTable->players[i].byIQ = playerTable->iq;
accountTable->players[i].wMainPart = playerTable->parts[PART_MAIN];
accountTable->players[i].wHairPart = playerTable->parts[PART_HAIR];
accountTable->players[i].x = playerTable->x;
accountTable->players[i].y = playerTable->y;
accountTable->players[i].skill_group = playerTable->skill_group;
accountTable->players[i].bChangeName = 0;
}
else
{
strlcpy(accountTable->players[i].szName, name, sizeof(accountTable->players[i].szName));
accountTable->players[i].byJob = static_cast<uint8_t>(job);
accountTable->players[i].byLevel = static_cast<uint8_t>(level);
accountTable->players[i].dwPlayMinutes = progressValue;
accountTable->players[i].byST = static_cast<uint8_t>(st);
accountTable->players[i].byHT = static_cast<uint8_t>(ht);
accountTable->players[i].byDX = static_cast<uint8_t>(dx);
accountTable->players[i].byIQ = static_cast<uint8_t>(iq);
accountTable->players[i].wMainPart = static_cast<uint16_t>(mainPart);
accountTable->players[i].wHairPart = static_cast<uint16_t>(hairPart);
accountTable->players[i].x = x;
accountTable->players[i].y = y;
accountTable->players[i].skill_group = static_cast<uint8_t>(skillGroup);
accountTable->players[i].bChangeName = static_cast<uint8_t>(changeName);
}
sys_log(0, "%s %lu %lu hair %u",
accountTable->players[i].szName,
accountTable->players[i].x,
accountTable->players[i].y,
accountTable->players[i].wHairPart);
return;
}
}
bool LoadAccountPlayerSummaries(uint32_t accountId, TAccountTable* accountTable)
{
CStmt stmt;
std::string query;
uint32_t playerId = 0;
char name[CHARACTER_NAME_MAX_LEN + 1] = {};
uint32_t job = 0;
uint32_t level = 0;
uint32_t progressValue = 0;
int32_t st = 0;
int32_t ht = 0;
int32_t dx = 0;
int32_t iq = 0;
uint32_t mainPart = 0;
uint32_t hairPart = 0;
int32_t x = 0;
int32_t y = 0;
uint32_t skillGroup = 0;
uint32_t changeName = 0;
if (g_stLocale == "gb2312")
{
query = std::string("SELECT id, name, job, level, alignment, st, ht, dx, iq, part_main, part_hair, x, y, skill_group, change_name FROM player")
+ GetTablePostfix() + " WHERE account_id=?";
}
else
{
query = std::string("SELECT id, name, job, level, playtime, st, ht, dx, iq, part_main, part_hair, x, y, skill_group, change_name FROM player")
+ GetTablePostfix() + " WHERE account_id=?";
}
if (!PreparePlayerStmt(stmt, query))
return false;
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
return false;
if (!stmt.BindResult(MYSQL_TYPE_LONG, &playerId)
|| !stmt.BindResult(MYSQL_TYPE_STRING, name, sizeof(name))
|| !stmt.BindResult(MYSQL_TYPE_LONG, &job)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &level)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &progressValue)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &st)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &ht)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &dx)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &iq)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &mainPart)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &hairPart)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &x)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &y)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &skillGroup)
|| !stmt.BindResult(MYSQL_TYPE_LONG, &changeName))
{
return false;
}
if (!stmt.Execute())
return false;
while (stmt.Fetch())
{
size_t nameLen = stmt.GetResultLength(1);
if (nameLen >= sizeof(name))
nameLen = sizeof(name) - 1;
name[nameLen] = '\0';
ApplyPlayerSummary(accountTable, playerId, name, job, level, progressValue, st, ht, dx, iq, mainPart, hairPart, x, y, skillGroup, changeName);
}
return true;
}
bool CountPlayerNames(const char* playerName, uint32_t playerId, uint32_t* count)
{
CStmt stmt;
std::string query;
if (g_stLocale == "sjis")
query = std::string("SELECT COUNT(*) FROM player") + GetTablePostfix() + " WHERE name=? collate sjis_japanese_ci AND id <> ?";
else
query = std::string("SELECT COUNT(*) FROM player") + GetTablePostfix() + " WHERE name=? AND id <> ?";
if (!PreparePlayerStmt(stmt, query))
return false;
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1)
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|| !stmt.BindResult(MYSQL_TYPE_LONG, count))
{
return false;
}
return stmt.Execute() && stmt.Fetch();
}
bool UpdatePlayerName(uint32_t playerId, const char* playerName)
{
CStmt stmt;
const std::string query = std::string("UPDATE player") + GetTablePostfix() + " SET name=?, change_name=0 WHERE id=?";
if (!PreparePlayerStmt(stmt, query))
return false;
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1)
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
{
return false;
}
return stmt.Execute();
}
}
bool CClientManager::InsertLogonAccount(const char * c_pszLogin, DWORD dwHandle, const char * c_pszIP)
{
char szLogin[LOGIN_MAX_LEN + 1];
@@ -110,23 +340,51 @@ void CClientManager::QUERY_LOGIN_BY_KEY(CPeer * pkPeer, DWORD dwHandle, TPacketG
return;
}
TAccountTable * pkTab = new TAccountTable;
memset(pkTab, 0, sizeof(TAccountTable));
TAccountTable accountTable = {};
accountTable.id = r.id;
trim_and_lower(r.login, accountTable.login, sizeof(accountTable.login));
strlcpy(accountTable.passwd, r.passwd, sizeof(accountTable.passwd));
strlcpy(accountTable.social_id, r.social_id, sizeof(accountTable.social_id));
strlcpy(accountTable.status, "OK", sizeof(accountTable.status));
pkTab->id = r.id;
trim_and_lower(r.login, pkTab->login, sizeof(pkTab->login));
strlcpy(pkTab->passwd, r.passwd, sizeof(pkTab->passwd));
strlcpy(pkTab->social_id, r.social_id, sizeof(pkTab->social_id));
strlcpy(pkTab->status, "OK", sizeof(pkTab->status));
sys_log(0, "LOGIN_BY_KEY success %s %lu %s", r.login, p->dwLoginKey, p->szIP);
ClientHandleInfo * info = new ClientHandleInfo(dwHandle);
info->pAccountTable = pkTab;
strlcpy(info->ip, p->szIP, sizeof(info->ip));
if (!LoadPlayerIndexTable(accountTable.id, &accountTable))
{
sys_log(0, "LOGIN_BY_KEY missing player_index for account %u", accountTable.id);
sys_log(0, "LOGIN_BY_KEY success %s %lu %s", r.login, p->dwLoginKey, info->ip);
char szQuery[QUERY_MAX_LEN];
snprintf(szQuery, sizeof(szQuery), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id=%u", GetTablePostfix(), r.id);
CDBManager::instance().ReturnQuery(szQuery, QID_LOGIN_BY_KEY, pkPeer->GetHandle(), info);
CreatePlayerIndexRow(accountTable.id);
if (!LoadPlayerIndexTable(accountTable.id, &accountTable))
{
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, dwHandle);
return;
}
}
if (!LoadAccountPlayerSummaries(accountTable.id, &accountTable))
{
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, dwHandle);
return;
}
if (!InsertLogonAccount(accountTable.login, pkPeer->GetHandle(), p->szIP))
{
sys_log(0, "RESULT_LOGIN: already logon %s", accountTable.login);
TPacketDGLoginAlready packet;
strlcpy(packet.szLogin, accountTable.login, sizeof(packet.szLogin));
pkPeer->EncodeHeader(DG::LOGIN_ALREADY, dwHandle, sizeof(TPacketDGLoginAlready));
pkPeer->Encode(&packet, sizeof(packet));
return;
}
if (CLoginData* loginData = GetLoginDataByLogin(accountTable.login))
memcpy(&loginData->GetAccountRef(), &accountTable, sizeof(TAccountTable));
pkPeer->EncodeHeader(DG::LOGIN_SUCCESS, dwHandle, sizeof(TAccountTable));
pkPeer->Encode(&accountTable, sizeof(TAccountTable));
}
void CClientManager::RESULT_LOGIN_BY_KEY(CPeer * peer, SQLMsg * msg)
@@ -464,44 +722,25 @@ void CClientManager::QUERY_LOGOUT(CPeer * peer, DWORD dwHandle,const char * data
void CClientManager::QUERY_CHANGE_NAME(CPeer * peer, DWORD dwHandle, TPacketGDChangeName * p)
{
char queryStr[QUERY_MAX_LEN];
uint32_t duplicateCount = 0;
if (g_stLocale == "sjis")
snprintf(queryStr, sizeof(queryStr),
"SELECT COUNT(*) as count FROM player%s WHERE name='%s' collate sjis_japanese_ci AND id <> %u",
GetTablePostfix(), p->name, p->pid);
else
snprintf(queryStr, sizeof(queryStr),
"SELECT COUNT(*) as count FROM player%s WHERE name='%s' AND id <> %u", GetTablePostfix(), p->name, p->pid);
auto pMsg = CDBManager::instance().DirectQuery(queryStr, SQL_PLAYER);
if (pMsg->Get()->uiNumRows)
{
if (!pMsg->Get()->pSQLResult)
{
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return;
}
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
if (*row[0] != '0')
{
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
return;
}
}
else
if (!CountPlayerNames(p->name, p->pid, &duplicateCount))
{
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return;
}
snprintf(queryStr, sizeof(queryStr),
"UPDATE player%s SET name='%s',change_name=0 WHERE id=%u", GetTablePostfix(), p->name, p->pid);
if (duplicateCount != 0)
{
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
return;
}
auto pMsg0 = CDBManager::instance().DirectQuery(queryStr, SQL_PLAYER);
if (!UpdatePlayerName(p->pid, p->name))
{
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return;
}
TPacketDGChangeName pdg;
peer->EncodeHeader(DG::CHANGE_NAME, dwHandle, sizeof(TPacketDGChangeName));
@@ -509,4 +748,3 @@ void CClientManager::QUERY_CHANGE_NAME(CPeer * peer, DWORD dwHandle, TPacketGDCh
strlcpy(pdg.name, p->name, sizeof(pdg.name));
peer->Encode(&pdg, sizeof(TPacketDGChangeName));
}

View File

@@ -128,6 +128,12 @@ std::unique_ptr<SQLMsg> CDBManager::DirectQuery(const char* c_pszQuery, int iSlo
return msg;
}
CAsyncSQL* CDBManager::GetDirectSQL(int iSlot)
{
assert(iSlot < SQL_MAX_NUM);
return m_directSQL[iSlot].get();
}
extern CPacketInfo g_query_info;
extern int g_query_count[2];
@@ -182,4 +188,3 @@ void CDBManager::QueryLocaleSet()
m_asyncSQL[n]->QueryLocaleSet();
}
}

View File

@@ -43,6 +43,7 @@ class CDBManager : public singleton<CDBManager>
void ReturnQuery(const char * c_pszQuery, int iType, IDENT dwIdent, void * pvData, int iSlot = SQL_PLAYER);
void AsyncQuery(const char * c_pszQuery, int iSlot = SQL_PLAYER);
std::unique_ptr<SQLMsg> DirectQuery(const char* c_pszQuery, int iSlot = SQL_PLAYER);
CAsyncSQL* GetDirectSQL(int iSlot = SQL_PLAYER);
SQLMsg * PopResult();
SQLMsg * PopResult(eSQL_SLOT slot );

View File

@@ -30,6 +30,12 @@ void CStmt::Destroy()
free(m_puiParamLen);
m_puiParamLen = NULL;
}
m_vec_param.clear();
m_vec_result.clear();
m_vec_result_len.clear();
m_uiParamCount = 0;
m_uiResultCount = 0;
}
void CStmt::Error(const char * c_pszMsg)
@@ -41,6 +47,8 @@ bool CStmt::Prepare(CAsyncSQL * sql, const char * c_pszQuery)
{
m_pkStmt = mysql_stmt_init(sql->GetSQLHandle());
m_stQuery = c_pszQuery;
m_uiParamCount = 0;
m_uiResultCount = 0;
if (mysql_stmt_prepare(m_pkStmt, m_stQuery.c_str(), m_stQuery.length()))
{
@@ -48,11 +56,7 @@ bool CStmt::Prepare(CAsyncSQL * sql, const char * c_pszQuery)
return false;
}
int iParamCount = 0;
for (unsigned int i = 0; i < m_stQuery.length(); ++i)
if (c_pszQuery[i] == '?')
++iParamCount;
const auto iParamCount = mysql_stmt_param_count(m_pkStmt);
if (iParamCount)
{
@@ -62,13 +66,12 @@ bool CStmt::Prepare(CAsyncSQL * sql, const char * c_pszQuery)
m_puiParamLen = (long unsigned int *) calloc(iParamCount, sizeof(long unsigned int));
}
m_vec_result.resize(48);
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * 48);
if (mysql_stmt_bind_result(m_pkStmt, &m_vec_result[0]))
const auto iFieldCount = mysql_stmt_field_count(m_pkStmt);
if (iFieldCount)
{
Error("mysql_stmt_bind_result");
return 0;
m_vec_result.resize(iFieldCount);
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * iFieldCount);
m_vec_result_len.resize(iFieldCount, 0);
}
return true;
@@ -114,6 +117,7 @@ bool CStmt::BindResult(enum_field_types type, void * p, int iMaxLen)
bind->buffer_type = type;
bind->buffer = (void *) p;
bind->buffer_length = iMaxLen;
bind->length = &m_vec_result_len[m_uiResultCount - 1];
return true;
}
@@ -130,9 +134,21 @@ int CStmt::Execute()
MYSQL_BIND * bind = &m_vec_param[i];
if (bind->buffer_type == MYSQL_TYPE_STRING)
{
*(m_puiParamLen + i) = strlen((const char *) bind->buffer);
sys_log(0, "param %d len %d buf %s", i, *m_puiParamLen, (const char *) bind->buffer);
}
if (!m_vec_result.empty())
{
if (m_uiResultCount != m_vec_result.size())
{
sys_log(0, "Result binding not enough %d, expected %d query: %s", m_uiResultCount, m_vec_result.size(), m_stQuery.c_str());
return 0;
}
if (mysql_stmt_bind_result(m_pkStmt, &m_vec_result[0]))
{
Error("mysql_stmt_bind_result");
return 0;
}
}
@@ -157,3 +173,10 @@ bool CStmt::Fetch()
return !mysql_stmt_fetch(m_pkStmt);
}
unsigned long CStmt::GetResultLength(unsigned int index) const
{
if (index >= m_vec_result_len.size())
return 0;
return m_vec_result_len[index];
}

View File

@@ -17,6 +17,7 @@ class CStmt
bool BindResult(enum_field_types type, void * p, int iMaxLen=0);
int Execute();
bool Fetch();
unsigned long GetResultLength(unsigned int index) const;
void Error(const char * c_pszMsg);
@@ -35,6 +36,7 @@ class CStmt
long unsigned int * m_puiParamLen;
std::vector<MYSQL_BIND> m_vec_result;
std::vector<unsigned long> m_vec_result_len;
unsigned int m_uiResultCount;
};