mangos/src/shared/Database/Database.cpp
Meltie2013 f9a203e0da
Revision Update (#56)
* Move Mangos revision into GitRevision namespace
2022-06-29 10:17:15 +01:00

784 lines
23 KiB
C++

/**
* MaNGOS is a full featured server for World of Warcraft, supporting
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
*
* Copyright (C) 2005-2022 MaNGOS <https://getmangos.eu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
* and lore are copyrighted by Blizzard Entertainment, Inc.
*/
#include "DatabaseEnv.h"
#include "Config/Config.h"
#include "Database/SqlOperations.h"
#include "GitRevision.h"
#include <ctime>
#include <iostream>
#include <fstream>
#include <memory>
#define MIN_CONNECTION_POOL_SIZE 1
#define MAX_CONNECTION_POOL_SIZE 16
struct DBVersion
{
std::string dbname;
std::string expected_version;
std::string expected_structure;
std::string minimal_expected_content; // Minimal because core can starts with some missing contents
std::string description;
};
const DBVersion databaseVersions[COUNT_DATABASES] = {
{ "World", GitRevision::GetWorldDBVersion(), GitRevision::GetWorldDBStructure(), GitRevision::GetWorldDBContent(), GitRevision::GetWorldDBUpdateDescription() }, // DATABASE_WORLD
{ "Realmd", GitRevision::GetRealmDBVersion(), GitRevision::GetRealmDBStructure(), GitRevision::GetRealmDBContent(), GitRevision::GetRealmDBUpdateDescription() }, // DATABASE_REALMD
{ "Character", GitRevision::GetCharDBVersion(), GitRevision::GetCharDBStructure(), GitRevision::GetCharDBContent(), GitRevision::GetCharDBUpdateDescription() }, // DATABASE_CHARACTER
};
//////////////////////////////////////////////////////////////////////////
SqlPreparedStatement* SqlConnection::CreateStatement(const std::string& fmt)
{
return new SqlPlainPreparedStatement(fmt, *this);
}
void SqlConnection::FreePreparedStatements()
{
SqlConnection::Lock guard(this);
size_t nStmts = m_holder.size();
for (size_t i = 0; i < nStmts; ++i)
{
delete m_holder[i];
}
m_holder.clear();
}
SqlPreparedStatement* SqlConnection::GetStmt(uint32 nIndex)
{
// resize stmt container
if (m_holder.size() <= nIndex)
{
m_holder.resize(nIndex + 1, NULL);
}
SqlPreparedStatement* pStmt = NULL;
// create stmt if needed
if (m_holder[nIndex] == NULL)
{
// obtain SQL request string
std::string fmt = m_db.GetStmtString(nIndex);
MANGOS_ASSERT(fmt.length());
// allocate SQlPreparedStatement object
pStmt = CreateStatement(fmt);
// prepare statement
if (!pStmt->prepare())
{
MANGOS_ASSERT(false && "Unable to prepare SQL statement");
return NULL;
}
// save statement in internal registry
m_holder[nIndex] = pStmt;
}
else
{
pStmt = m_holder[nIndex];
}
return pStmt;
}
bool SqlConnection::ExecuteStmt(int nIndex, const SqlStmtParameters& id)
{
if (nIndex == -1)
{
return false;
}
// get prepared statement object
SqlPreparedStatement* pStmt = GetStmt(nIndex);
// bind parameters
pStmt->bind(id);
// execute statement
return pStmt->execute();
}
//////////////////////////////////////////////////////////////////////////
Database::~Database()
{
StopServer();
}
bool Database::Initialize(const char* infoString, int nConns /*= 1*/)
{
// Enable logging of SQL commands (usually only GM commands)
// (See method: PExecuteLog)
m_logSQL = sConfig.GetBoolDefault("LogSQL", false);
m_logsDir = sConfig.GetStringDefault("LogsDir", "");
if (!m_logsDir.empty())
{
if ((m_logsDir.at(m_logsDir.length() - 1) != '/') && (m_logsDir.at(m_logsDir.length() - 1) != '\\'))
{
m_logsDir.append("/");
}
}
m_pingIntervallms = sConfig.GetIntDefault("MaxPingTime", 30) * (MINUTE * 1000);
// create DB connections
// setup connection pool size
if (nConns < MIN_CONNECTION_POOL_SIZE)
{
m_nQueryConnPoolSize = MIN_CONNECTION_POOL_SIZE;
}
else if (nConns > MAX_CONNECTION_POOL_SIZE)
{
m_nQueryConnPoolSize = MAX_CONNECTION_POOL_SIZE;
}
else
{
m_nQueryConnPoolSize = nConns;
}
// create connection pool for sync requests
for (int i = 0; i < m_nQueryConnPoolSize; ++i)
{
SqlConnection* pConn = CreateConnection();
if (!pConn->Initialize(infoString))
{
delete pConn;
return false;
}
m_pQueryConnections.push_back(pConn);
}
// create and initialize connection for async requests
m_pAsyncConn = CreateConnection();
if (!m_pAsyncConn->Initialize(infoString))
{
return false;
}
m_pResultQueue = new SqlResultQueue;
InitDelayThread();
return true;
}
void Database::StopServer()
{
HaltDelayThread();
delete m_pResultQueue;
delete m_pAsyncConn;
m_pResultQueue = NULL;
m_pAsyncConn = NULL;
for (size_t i = 0; i < m_pQueryConnections.size(); ++i)
{
delete m_pQueryConnections[i];
}
m_pQueryConnections.clear();
}
SqlDelayThread* Database::CreateDelayThread()
{
assert(m_pAsyncConn);
return new SqlDelayThread(this, m_pAsyncConn);
}
void Database::InitDelayThread()
{
assert(!m_delayThread);
// New delay thread for delay execute
m_threadBody = CreateDelayThread(); // will deleted at m_delayThread delete
m_TransStorage = new ACE_TSS<Database::TransHelper>();
m_delayThread = new ACE_Based::Thread(m_threadBody);
}
void Database::HaltDelayThread()
{
if (!m_threadBody || !m_delayThread)
{
return;
}
m_threadBody->Stop(); // Stop event
m_delayThread->wait(); // Wait for flush to DB
delete m_TransStorage;
delete m_delayThread; // This also deletes m_threadBody
m_delayThread = NULL;
m_threadBody = NULL;
m_TransStorage=NULL;
}
void Database::ThreadStart()
{
}
void Database::ThreadEnd()
{
}
void Database::ProcessResultQueue()
{
if (m_pResultQueue)
{
m_pResultQueue->Update();
}
}
void Database::escape_string(std::string& str)
{
if (str.empty())
{
return;
}
char* buf = new char[str.size() * 2 + 1];
// we don't care what connection to use - escape string will be the same
m_pQueryConnections[0]->escape_string(buf, str.c_str(), str.size());
str = buf;
delete[] buf;
}
SqlConnection* Database::getQueryConnection()
{
int nCount = 0;
if (m_nQueryCounter == long(1 << 31))
{
m_nQueryCounter = 0;
}
else
{
nCount = ++m_nQueryCounter;
}
return m_pQueryConnections[nCount % m_nQueryConnPoolSize];
}
void Database::Ping()
{
const char* sql = "SELECT 1";
{
SqlConnection::Lock guard(m_pAsyncConn);
delete guard->Query(sql);
}
for (int i = 0; i < m_nQueryConnPoolSize; ++i)
{
SqlConnection::Lock guard(m_pQueryConnections[i]);
delete guard->Query(sql);
}
}
bool Database::PExecuteLog(const char* format, ...)
{
if (!format)
{
return false;
}
va_list ap;
char szQuery [MAX_QUERY_LEN];
va_start(ap, format);
int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
va_end(ap);
if (res == -1)
{
sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
return false;
}
if (m_logSQL)
{
time_t curr;
tm local;
time(&curr); // get current time_t value
local = *(localtime(&curr)); // dereference and assign
char fName[128];
sprintf(fName, "%04d-%02d-%02d_logSQL.sql", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday);
FILE* log_file;
std::string logsDir_fname = m_logsDir + fName;
log_file = fopen(logsDir_fname.c_str(), "a");
if (log_file)
{
fprintf(log_file, "%s;\n", szQuery);
fclose(log_file);
}
else
{
// The file could not be opened
sLog.outError("SQL-Logging is disabled - Log file for the SQL commands could not be openend: %s", fName);
}
}
return Execute(szQuery);
}
QueryResult* Database::PQuery(const char* format, ...)
{
if (!format)
{
return NULL;
}
va_list ap;
char szQuery [MAX_QUERY_LEN];
va_start(ap, format);
int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
va_end(ap);
if (res == -1)
{
sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
return NULL;
}
return Query(szQuery);
}
QueryNamedResult* Database::PQueryNamed(const char* format, ...)
{
if (!format)
{
return NULL;
}
va_list ap;
char szQuery [MAX_QUERY_LEN];
va_start(ap, format);
int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
va_end(ap);
if (res == -1)
{
sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
return NULL;
}
return QueryNamed(szQuery);
}
bool Database::Execute(const char* sql)
{
if (!m_pAsyncConn)
{
return false;
}
SqlTransaction* pTrans = (*m_TransStorage)->get();
if (pTrans)
{
// add SQL request to trans queue
pTrans->DelayExecute(new SqlPlainRequest(sql));
}
else
{
// if async execution is not available
if (!m_bAllowAsyncTransactions)
{
return DirectExecute(sql);
}
// Simple sql statement
m_threadBody->Delay(new SqlPlainRequest(sql));
}
return true;
}
bool Database::PExecute(const char* format, ...)
{
if (!format)
{
return false;
}
va_list ap;
char szQuery [MAX_QUERY_LEN];
va_start(ap, format);
int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
va_end(ap);
if (res == -1)
{
sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
return false;
}
return Execute(szQuery);
}
bool Database::DirectPExecute(const char* format, ...)
{
if (!format)
{
return false;
}
va_list ap;
char szQuery [MAX_QUERY_LEN];
va_start(ap, format);
int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
va_end(ap);
if (res == -1)
{
sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
return false;
}
return DirectExecute(szQuery);
}
bool Database::BeginTransaction()
{
if (!m_pAsyncConn)
{
return false;
}
// initiate transaction on current thread
// currently we do not support queued transactions
(*m_TransStorage)->init();
return true;
}
bool Database::CommitTransaction()
{
if (!m_pAsyncConn)
{
return false;
}
// check if we have pending transaction
if (!(*m_TransStorage)->get())
{
return false;
}
// if async execution is not available
if (!m_bAllowAsyncTransactions)
{
return CommitTransactionDirect();
}
// add SqlTransaction to the async queue
m_threadBody->Delay((*m_TransStorage)->detach());
return true;
}
bool Database::CommitTransactionDirect()
{
if (!m_pAsyncConn)
{
return false;
}
// check if we have pending transaction
if (!(*m_TransStorage)->get())
{
return false;
}
// directly execute SqlTransaction
SqlTransaction* pTrans = (*m_TransStorage)->detach();
pTrans->Execute(m_pAsyncConn);
delete pTrans;
return true;
}
bool Database::RollbackTransaction()
{
if (!m_pAsyncConn)
{
return false;
}
if (!(*m_TransStorage)->get())
{
return false;
}
// remove scheduled transaction
(*m_TransStorage)->reset();
return true;
}
void PrintNormalYouHaveDatabaseVersion(std::string current_db_version, std::string current_db_structure, std::string current_db_content, std::string description)
{
sLog.outString(" [A] You have database Version: %s", current_db_version.c_str());
sLog.outString(" Structure: %s", current_db_structure.c_str());
sLog.outString(" Content: %s", current_db_content.c_str());
sLog.outString(" Description: %s", description.c_str());
}
void PrintErrorYouHaveDatabaseVersion(std::string current_db_version, std::string current_db_structure, std::string current_db_content, std::string description)
{
sLog.outErrorDb(" [A] You have database Version: %s", current_db_version.c_str());
sLog.outErrorDb(" Structure: %s", current_db_structure.c_str());
sLog.outErrorDb(" Content: %s", current_db_content.c_str());
sLog.outErrorDb(" Description: %s", description.c_str());
}
void PrintNormalDatabaseVersionReferencedByCore(const DBVersion& core_db_requirements)
{
sLog.outString(" [B] The core references last database Version: %s", core_db_requirements.expected_version.c_str());
sLog.outString(" Structure: %s", core_db_requirements.expected_structure.c_str());
sLog.outString(" Content: %s", core_db_requirements.minimal_expected_content.c_str());
sLog.outString(" Description: %s", core_db_requirements.description.c_str());
}
void PrintErrorYouNeedDatabaseVersionExpectedByCore(const DBVersion& core_db_requirements)
{
sLog.outErrorDb(" [B] The core needs database Version: %s", core_db_requirements.expected_version.c_str());
sLog.outErrorDb(" Structure: %s", core_db_requirements.expected_structure.c_str());
sLog.outErrorDb(" Content: %s", core_db_requirements.minimal_expected_content.c_str());
sLog.outErrorDb(" Description: %s", core_db_requirements.description.c_str());
}
bool Database::CheckDatabaseVersion(DatabaseTypes database)
{
const DBVersion& core_db_requirements = databaseVersions[database];
// Fetch the database version table information
QueryResult* result = Query("SELECT `version`, `structure`, `content`, `description` FROM `db_version` ORDER BY `version` DESC, `structure` DESC, `content` DESC LIMIT 1");
// db_version table does not exist or is empty
if (!result)
{
sLog.outErrorDb("The table `db_version` in your [%s] database is missing or corrupt.", core_db_requirements.dbname.c_str());
sLog.outErrorDb();
sLog.outErrorDb(" [A] You have database Version: MaNGOS can not verify your database version or its existence!");
sLog.outErrorDb();
PrintErrorYouNeedDatabaseVersionExpectedByCore(core_db_requirements);
sLog.outErrorDb();
sLog.outErrorDb("Please verify your database location or your database integrity.");
// The core loading will no go further :
return false;
}
Field* fields = result->Fetch();
std::string current_db_version = fields[0].GetCppString();
std::string current_db_structure = fields[1].GetCppString();
std::string current_db_content = fields[2].GetCppString();
std::string description = fields[3].GetCppString();
delete result;
// Structure does not match the required version
if (current_db_version != core_db_requirements.expected_version || current_db_structure != core_db_requirements.expected_structure)
{
sLog.outErrorDb("The table `db_version` indicates that your [%s] database does not match the expected structure!", core_db_requirements.dbname.c_str());
sLog.outErrorDb();
PrintErrorYouHaveDatabaseVersion(current_db_version, current_db_structure, current_db_content, description);
sLog.outErrorDb();
PrintErrorYouNeedDatabaseVersionExpectedByCore(core_db_requirements);
sLog.outErrorDb();
sLog.outErrorDb("You must apply all updates after [A] to [B] to use MaNGOS with this database.");
sLog.outErrorDb("These updates are included in the database/%s/Updates folder.", core_db_requirements.dbname.c_str());
return false;
}
bool db_vs_core_content_version_mismatch = false;
// DB is not up to date, but structure is correct.
// The 'content' version in the 'db_version' table can be < from the one required by the core
// See enum values for :
// WORLD_DB_CONTENT_NR
// CHAR_DB_CONTENT_NR
// REALMD_DB_CONTENT_NR
// for more information.
if (current_db_content < core_db_requirements.minimal_expected_content)
{
// TODO : Should not display with error color but warning (e.g YELLOW) => Create a sLog.outWarningDb() and sLog.outWarning()
sLog.outErrorDb("You have not updated the core for few DB [%s] updates!", core_db_requirements.dbname.c_str());
sLog.outErrorDb("Current DB content is %s, core expects %s", current_db_content.c_str(), core_db_requirements.minimal_expected_content.c_str());
sLog.outErrorDb("It is recommended to run ALL database updates up to the required core version.");
sLog.outErrorDb("These updates are included in the database/%s/Updates folder.", core_db_requirements.dbname.c_str());
sLog.outErrorDb("This is ok for now but should not last long.");
db_vs_core_content_version_mismatch = true;
}
// Do not alert if current_db_content > core_db_requirements.minimal_expected_content it can mislead newcomers !
// In anys cases if there are differences in content : output a recap of the differences :
if (db_vs_core_content_version_mismatch)
{
// TODO : Should not display with error color but warning (e.g YELLOW) => Create a sLog.outWarningDb() and sLog.outWarning()
sLog.outErrorDb("The table `db_version` indicates that your [%s] database does not match the expected version!", core_db_requirements.dbname.c_str());
sLog.outErrorDb();
PrintErrorYouHaveDatabaseVersion(current_db_version, current_db_structure, current_db_content, description);
sLog.outErrorDb();
PrintErrorYouNeedDatabaseVersionExpectedByCore(core_db_requirements);
}
else
{
if (current_db_version == core_db_requirements.expected_version && current_db_structure == core_db_requirements.expected_structure)
{
sLog.outString("The table `db_version` indicates that your [%s] database has the same version as the core requirements.", core_db_requirements.dbname.c_str());
sLog.outString();
}
else
{
sLog.outString("The table `db_version` indicates that your [%s] database has a higher version than the one referenced by the core."
"\nYou have probably applied DB updates, and that's a good thing to keep your server up to date.", core_db_requirements.dbname.c_str());
sLog.outString();
PrintNormalYouHaveDatabaseVersion(current_db_version, current_db_structure, current_db_content, description);
sLog.outString();
PrintNormalDatabaseVersionReferencedByCore(core_db_requirements);
sLog.outString();
sLog.outString("You can run the core without any problem like that.");
sLog.outString();
}
}
return true;
}
bool Database::ExecuteStmt(const SqlStatementID& id, SqlStmtParameters* params)
{
if (!m_pAsyncConn)
{
return false;
}
SqlTransaction* pTrans = (*m_TransStorage)->get();
if (pTrans)
{
// add SQL request to trans queue
pTrans->DelayExecute(new SqlPreparedRequest(id.ID(), params));
}
else
{
// if async execution is not available
if (!m_bAllowAsyncTransactions)
{
return DirectExecuteStmt(id, params);
}
// Simple sql statement
m_threadBody->Delay(new SqlPreparedRequest(id.ID(), params));
}
return true;
}
bool Database::DirectExecuteStmt(const SqlStatementID& id, SqlStmtParameters* params)
{
MANGOS_ASSERT(params);
std::shared_ptr<SqlStmtParameters> p(params);
// execute statement
SqlConnection::Lock _guard(getAsyncConnection());
return _guard->ExecuteStmt(id.ID(), *params);
}
SqlStatement Database::CreateStatement(SqlStatementID& index, const char* fmt)
{
int nId = -1;
// check if statement ID is initialized
if (!index.initialized())
{
// convert to lower register
std::string szFmt(fmt);
// count input parameters
int nParams = std::count(szFmt.begin(), szFmt.end(), '?');
// find existing or add a new record in registry
LOCK_GUARD _guard(m_stmtGuard);
MANGOS_ASSERT(_guard.locked());
PreparedStmtRegistry::const_iterator iter = m_stmtRegistry.find(szFmt);
if (iter == m_stmtRegistry.end())
{
nId = ++m_iStmtIndex;
m_stmtRegistry[szFmt] = nId;
}
else
{
nId = iter->second;
}
// save initialized statement index info
index.init(nId, nParams);
}
return SqlStatement(index, *this);
}
std::string Database::GetStmtString(const int stmtId) const
{
if (stmtId == -1 || stmtId > m_iStmtIndex)
{
return std::string();
}
LOCK_GUARD _guard(m_stmtGuard);
if (_guard.locked())
{
PreparedStmtRegistry::const_iterator iter_last = m_stmtRegistry.end();
for (PreparedStmtRegistry::const_iterator iter = m_stmtRegistry.begin(); iter != iter_last; ++iter)
{
if (iter->second == stmtId)
{
return iter->first;
}
}
}
return std::string();
}
// HELPER CLASSES AND FUNCTIONS
Database::TransHelper::~TransHelper()
{
reset();
}
SqlTransaction* Database::TransHelper::init()
{
MANGOS_ASSERT(!m_pTrans); // if we will get a nested transaction request - we MUST fix code!!!
m_pTrans = new SqlTransaction;
return m_pTrans;
}
SqlTransaction* Database::TransHelper::detach()
{
SqlTransaction* pRes = m_pTrans;
m_pTrans = NULL;
return pRes;
}
void Database::TransHelper::reset()
{
delete m_pTrans;
m_pTrans = NULL;
}