feat(Core/Database): enable modules to own their database (#27544)

Co-authored-by: UndeadRogue <[email protected]>
Co-authored-by: UltraNix <[email protected]>
Co-authored-by: 郑佩茹 <[email protected]>
Co-authored-by: Yunfan Li <[email protected]>
Co-authored-by: kadeshar <[email protected]>
Co-authored-by: sudlud <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Keleborn
2026-09-14 09:20:44 +02:00
committed by GitHub
co-authored by UndeadRogue UltraNix 郑佩茹 Yunfan Li kadeshar sudlud Claude Opus 5
parent e962278b9b
commit e1823bb2db
14 changed files with 710 additions and 54 deletions
+9
View File
@@ -446,6 +446,9 @@ bool StartDB()
if (!loader.Load())
return false;
if (!sScriptMgr->OnModuleDatabasesLoading())
return false;
///- Get the realm Id from the configuration file
realm.Id.Realm = sConfigMgr->GetOption<uint32>("RealmID", 1);
if (!realm.Id.Realm)
@@ -494,6 +497,8 @@ void StopDB()
WorldDatabase.Close();
LoginDatabase.Close();
sScriptMgr->OnModuleDatabasesClosing();
MySQL::Library_End();
}
@@ -584,6 +589,8 @@ void WorldUpdateLoop()
CharacterDatabase.WarnAboutSyncQueries(true);
WorldDatabase.WarnAboutSyncQueries(true);
sScriptMgr->OnDatabaseWarnAboutSyncQueries(true);
///- While we have not World::m_stopEvent, update the world
while (!World::IsStopped())
{
@@ -613,6 +620,8 @@ void WorldUpdateLoop()
#endif
}
sScriptMgr->OnDatabaseWarnAboutSyncQueries(false);
LoginDatabase.WarnAboutSyncQueries(false);
CharacterDatabase.WarnAboutSyncQueries(false);
WorldDatabase.WarnAboutSyncQueries(false);
@@ -0,0 +1,37 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef DATABASE_UPDATE_POOL_H
#define DATABASE_UPDATE_POOL_H
#include "Define.h"
#include "MySQLConnection.h"
#include "QueryResult.h"
#include <string_view>
// Minimal pool interface the DB updater operates on. Core pools reach it through
// DatabaseWorkerPoolAdapter; modules can implement it directly (see ModuleDatabasePool).
struct AC_DATABASE_API DatabaseUpdatePool
{
virtual ~DatabaseUpdatePool() = default;
virtual void DirectExecute(std::string_view query) = 0;
virtual QueryResult Query(std::string_view query) = 0;
virtual MySQLConnectionInfo const* GetConnectionInfo() const = 0;
};
#endif
@@ -0,0 +1,49 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef DATABASE_WORKER_POOL_ADAPTER_H
#define DATABASE_WORKER_POOL_ADAPTER_H
#include "DatabaseUpdatePool.h"
#include "DatabaseWorkerPool.h"
template <class T>
class DatabaseWorkerPoolAdapter : public DatabaseUpdatePool
{
public:
DatabaseWorkerPoolAdapter(DatabaseWorkerPool<T>& pool) : _pool(pool) {}
void DirectExecute(std::string_view query) override
{
_pool.DirectExecute(query);
}
QueryResult Query(std::string_view query) override
{
return _pool.Query(query);
}
MySQLConnectionInfo const* GetConnectionInfo() const override
{
return _pool.GetConnectionInfo();
}
private:
DatabaseWorkerPool<T>& _pool;
};
#endif
@@ -0,0 +1,264 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "ModuleDatabasePool.h"
#include "Errors.h"
#include "Log.h"
#include "MySQLConnection.h"
#include "MySQLPreparedStatement.h"
#include "QueryResult.h"
#include "Transaction.h"
#include <errmsg.h>
#include <limits>
#include <mysqld_error.h>
#include <thread>
ModuleDatabasePool::ModuleDatabasePool()
: _connectionInfo(""), _synchThreads(0)
{
}
ModuleDatabasePool::~ModuleDatabasePool()
{
Close();
}
void ModuleDatabasePool::SetConnectionInfo(std::string_view infoString, uint8 synchThreads)
{
_connectionInfo = MySQLConnectionInfo(infoString);
_synchThreads = synchThreads;
}
uint32 ModuleDatabasePool::Open()
{
if (!_synchThreads)
{
LOG_ERROR("sql.driver", "ModuleDatabasePool: database `{}` was configured with 0 synchronous connections, "
"at least one is required.", _connectionInfo.database);
return CR_UNKNOWN_ERROR;
}
Close();
for (uint8 i = 0; i < _synchThreads; ++i)
{
auto conn = std::unique_ptr<MySQLConnection>(CreateConnection(_connectionInfo));
uint32 result = conn->Open();
if (result != 0)
{
LOG_ERROR("sql.driver", "ModuleDatabasePool: could not open connection {}/{} to database `{}`, error {}",
i + 1, _synchThreads, _connectionInfo.database, result);
Close();
return result;
}
_connections.push_back(std::move(conn));
}
return 0;
}
bool ModuleDatabasePool::PrepareStatements()
{
for (auto const& conn : _connections)
{
conn->LockIfReady();
if (!conn->PrepareStatements())
{
conn->Unlock();
Close();
return false;
}
conn->Unlock();
}
if (!_connections.empty())
{
MySQLConnection const* conn = _connections.front().get();
_preparedStatementSize.assign(conn->m_stmts.size(), 0);
for (std::size_t i = 0; i < conn->m_stmts.size(); ++i)
{
if (MySQLPreparedStatement* stmt = conn->m_stmts[i].get())
{
uint32 const paramCount = stmt->GetParameterCount();
ASSERT(paramCount < std::numeric_limits<uint8>::max());
_preparedStatementSize[i] = static_cast<uint8>(paramCount);
}
}
}
return true;
}
void ModuleDatabasePool::Close()
{
_connections.clear();
_preparedStatementSize.clear();
}
void ModuleDatabasePool::Execute(std::string_view sql)
{
// Synchronous for now - kept separate from DirectExecute so async execution
// can be added later without touching callers.
DirectExecute(sql);
}
void ModuleDatabasePool::DirectExecute(std::string_view sql)
{
if (sql.empty())
return;
if (_connections.empty())
return;
MySQLConnection* conn = GetFreeConnection();
conn->Execute(sql);
conn->Unlock();
}
QueryResult ModuleDatabasePool::Query(std::string_view sql)
{
if (_connections.empty())
return QueryResult(nullptr);
MySQLConnection* conn = GetFreeConnection();
ResultSet* result = conn->Query(sql);
conn->Unlock();
// Mirror DatabaseWorkerPool<T>::Query semantics: nullptr for empty results,
// and the first row loaded before the result is handed out.
if (!result || !result->GetRowCount() || !result->NextRow())
{
delete result;
return QueryResult(nullptr);
}
return QueryResult(result);
}
void ModuleDatabasePool::Execute(PreparedStatementBase* stmt)
{
if (_connections.empty())
{
delete stmt;
return;
}
MySQLConnection* conn = GetFreeConnection();
conn->Execute(stmt);
conn->Unlock();
delete stmt;
}
PreparedQueryResult ModuleDatabasePool::Query(PreparedStatementBase* stmt)
{
if (_connections.empty())
{
delete stmt;
return PreparedQueryResult(nullptr);
}
MySQLConnection* conn = GetFreeConnection();
PreparedResultSet* result = conn->Query(stmt);
conn->Unlock();
//! Delete proxy-class. Not needed anymore
delete stmt;
if (!result || !result->GetRowCount())
{
delete result;
return PreparedQueryResult(nullptr);
}
return PreparedQueryResult(result);
}
uint8 ModuleDatabasePool::GetPreparedStatementParamCount(uint32 index) const
{
return index < _preparedStatementSize.size() ? _preparedStatementSize[index] : 0;
}
void ModuleDatabasePool::DirectCommitTransaction(std::shared_ptr<TransactionBase> transaction)
{
if (_connections.empty())
return;
MySQLConnection* conn = GetFreeConnection();
int errorCode = conn->ExecuteTransaction(transaction);
if (!errorCode)
{
conn->Unlock();
return;
}
//! Handle MySQL Errno 1213 without extending deadlock to the core itself
if (errorCode == ER_LOCK_DEADLOCK)
{
uint8 constexpr loopBreaker = 5;
for (uint8 i = 0; i < loopBreaker; ++i)
{
if (!conn->ExecuteTransaction(transaction))
break;
}
}
transaction->Cleanup();
conn->Unlock();
}
void ModuleDatabasePool::KeepAlive()
{
//! Ping connections that are not busy; a locked connection is in use and alive.
for (auto const& conn : _connections)
{
if (conn->LockIfReady())
{
conn->Ping();
conn->Unlock();
}
}
}
MySQLConnection* ModuleDatabasePool::GetFreeConnection()
{
uint8 i = 0;
auto const num_cons = _connections.size();
MySQLConnection* connection = nullptr;
//! Block forever until a connection is free
for (;;)
{
connection = _connections[++i % num_cons].get();
//! Must be matched with connection->Unlock() or you will get deadlocks
if (connection->LockIfReady())
break;
if (i % num_cons == 0)
std::this_thread::yield();
}
return connection;
}
MySQLConnectionInfo const* ModuleDatabasePool::GetConnectionInfo() const
{
return &_connectionInfo;
}
@@ -0,0 +1,122 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef MODULE_DATABASE_POOL_H
#define MODULE_DATABASE_POOL_H
#include "DatabaseEnvFwd.h"
#include "DatabaseUpdatePool.h"
#include "Define.h"
#include "MySQLConnection.h"
#include "PreparedStatement.h"
#include "StringFormat.h"
#include <memory>
#include <string_view>
#include <vector>
class TransactionBase;
// Base class for module-owned database pools. A module derives from this,
// implements CreateConnection with its own MySQLConnection subclass (carrying
// the module's prepared statements), and gets open/execute/query plus DBUpdater
// compatibility without any core-side registration.
//
// Connections are synchronous; asynchronous execution can be added in a
// follow-up without changing this interface. DoPrepareStatements must mark every
// statement CONNECTION_SYNCH: a CONNECTION_ASYNC one is skipped on these
// connections and asserts on first use.
class AC_DATABASE_API ModuleDatabasePool : public DatabaseUpdatePool
{
public:
ModuleDatabasePool();
virtual ~ModuleDatabasePool();
void SetConnectionInfo(std::string_view infoString, uint8 synchThreads);
//! Opens the configured number of synchronous connections.
//! Returns 0 on success, or the MySQL error code of the first failed connection.
uint32 Open();
//! Prepares the connection statements. Call after the schema exists
//! (post create/populate/update), mirroring DatabaseLoader's ordering.
bool PrepareStatements();
void Close();
void Execute(std::string_view sql);
void DirectExecute(std::string_view sql) override;
QueryResult Query(std::string_view sql) override;
MySQLConnectionInfo const* GetConnectionInfo() const override;
//! Format variants, mirroring DatabaseWorkerPool.
template<typename... Args>
void Execute(std::string_view sql, Args&&... args)
{
if (sql.empty())
return;
Execute(std::string_view(Acore::StringFormat(sql, std::forward<Args>(args)...)));
}
template<typename... Args>
void DirectExecute(std::string_view sql, Args&&... args)
{
if (sql.empty())
return;
DirectExecute(std::string_view(Acore::StringFormat(sql, std::forward<Args>(args)...)));
}
template<typename... Args>
QueryResult Query(std::string_view sql, Args&&... args)
{
if (sql.empty())
return QueryResult(nullptr);
return Query(std::string_view(Acore::StringFormat(sql, std::forward<Args>(args)...)));
}
//! Prepared statements. The index space is defined by the module's connection
//! class (DoPrepareStatements); parameter counts are recorded by PrepareStatements(),
//! so building one before that call yields a zero-parameter statement.
//! Both calls consume (delete) the statement, mirroring DatabaseWorkerPool.
void Execute(PreparedStatementBase* stmt);
PreparedQueryResult Query(PreparedStatementBase* stmt);
//! Parameter count for a prepared statement index, for constructing typed
//! PreparedStatement<T> objects module-side.
[[nodiscard]] uint8 GetPreparedStatementParamCount(uint32 index) const;
//! Synchronously commits the transaction on a free connection.
void DirectCommitTransaction(std::shared_ptr<TransactionBase> transaction);
//! Pings every idle connection to keep them alive.
void KeepAlive();
protected:
virtual MySQLConnection* CreateConnection(MySQLConnectionInfo& connInfo) = 0;
private:
MySQLConnection* GetFreeConnection();
MySQLConnectionInfo _connectionInfo;
std::vector<std::unique_ptr<MySQLConnection>> _connections;
std::vector<uint8> _preparedStatementSize;
uint8 _synchThreads;
};
#endif
@@ -56,6 +56,7 @@ class AC_DATABASE_API MySQLConnection
template <class T>
friend class DatabaseWorkerPool;
friend class ModuleDatabasePool;
friend class PingOperation;
public:
@@ -31,6 +31,7 @@ class AC_DATABASE_API TransactionBase
{
friend class TransactionTask;
friend class MySQLConnection;
friend class ModuleDatabasePool;
template <typename T>
friend class DatabaseWorkerPool;
+125 -46
View File
@@ -20,6 +20,7 @@
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "DatabaseWorkerPoolAdapter.h"
#include "Log.h"
#include "StartProcess.h"
#include "UpdateFetcher.h"
@@ -93,10 +94,16 @@ std::string DBUpdater<LoginDatabaseConnection>::GetTableName()
return "Auth";
}
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetSourceDirectory()
{
return BuiltInConfig::GetSourceDirectory();
}
template<>
std::string DBUpdater<LoginDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_auth/";
return DBUpdater<LoginDatabaseConnection>::GetSourceDirectory() + "/data/sql/base/db_auth/";
}
template<>
@@ -126,10 +133,16 @@ std::string DBUpdater<WorldDatabaseConnection>::GetTableName()
return "World";
}
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetSourceDirectory()
{
return BuiltInConfig::GetSourceDirectory();
}
template<>
std::string DBUpdater<WorldDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_world/";
return DBUpdater<WorldDatabaseConnection>::GetSourceDirectory() + "/data/sql/base/db_world/";
}
template<>
@@ -159,10 +172,16 @@ std::string DBUpdater<CharacterDatabaseConnection>::GetTableName()
return "Character";
}
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetSourceDirectory()
{
return BuiltInConfig::GetSourceDirectory();
}
template<>
std::string DBUpdater<CharacterDatabaseConnection>::GetBaseFilesDirectory()
{
return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_characters/";
return DBUpdater<CharacterDatabaseConnection>::GetSourceDirectory() + "/data/sql/base/db_characters/";
}
template<>
@@ -186,8 +205,19 @@ BaseLocation DBUpdater<T>::GetBaseLocationType()
return LOCATION_REPOSITORY;
}
template<class T>
bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)
namespace
{
using Path = std::filesystem::path;
QueryResult Retrieve(DatabaseUpdatePool& pool, std::string const& query);
void Apply(DatabaseUpdatePool& pool, std::string const& query);
void ApplyFile(DatabaseUpdatePool& pool, Path const& path);
void ApplyFile(DatabaseUpdatePool& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database,
std::string const& ssl, Path const& path);
bool CreateDatabase(DatabaseUpdatePool& pool)
{
LOG_WARN("sql.updates", "Database \"{}\" does not exist", pool.GetConnectionInfo()->database);
@@ -220,8 +250,9 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)
try
{
DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password,
pool.GetConnectionInfo()->port_or_socket, "", pool.GetConnectionInfo()->ssl, temp);
ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user,
pool.GetConnectionInfo()->password, pool.GetConnectionInfo()->port_or_socket, "",
pool.GetConnectionInfo()->ssl, temp);
}
catch (UpdateException&)
{
@@ -236,15 +267,14 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)
return true;
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesList /*= {}*/)
bool UpdateDatabase(DatabaseUpdatePool& pool, DBUpdaterInfo const& info, std::string_view modulesList)
{
if (!DBUpdaterUtil::CheckExecutable())
return false;
LOG_INFO("sql.updates", "Updating {} database...", DBUpdater<T>::GetTableName());
LOG_INFO("sql.updates", "Updating {} database...", info.displayName);
Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());
Path const sourceDirectory(info.sourceDirectory);
if (!is_directory(sourceDirectory))
{
@@ -255,16 +285,16 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesL
auto CheckUpdateTable = [&](std::string const& tableName)
{
auto checkTable = DBUpdater<T>::Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
auto checkTable = Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
if (!checkTable)
{
LOG_WARN("sql.updates", "> Table '{}' not exist! Try add based table", tableName);
Path const temp(GetBaseFilesDirectory() + tableName + ".sql");
Path const temp = Path(info.baseFilesDirectory) / (tableName + ".sql");
try
{
DBUpdater<T>::ApplyFile(pool, temp);
ApplyFile(pool, temp);
}
catch (UpdateException&)
{
@@ -281,9 +311,9 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesL
if (!CheckUpdateTable("updates") || !CheckUpdateTable("updates_include"))
return false;
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { DBUpdater<T>::Apply(pool, query); },
[&](Path const & file) { DBUpdater<T>::ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); }, DBUpdater<T>::GetDBModuleName(), modulesList);
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { Apply(pool, query); },
[&](Path const & file) { ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return Retrieve(pool, query); }, info.dbModuleName, modulesList);
UpdateResult result;
try
@@ -299,27 +329,27 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesL
return false;
}
std::string const info = Acore::StringFormat("Containing {} new and {} archived updates.", result.recent, result.archived);
std::string const summary = Acore::StringFormat("Containing {} new and {} archived updates.", result.recent, result.archived);
if (!result.updated)
LOG_INFO("sql.updates", ">> {} database is up-to-date! {}", DBUpdater<T>::GetTableName(), info);
LOG_INFO("sql.updates", ">> {} database is up-to-date! {}", info.displayName, summary);
else
LOG_INFO("sql.updates", ">> Applied {} {}. {}", result.updated, result.updated == 1 ? "query" : "queries", info);
LOG_INFO("sql.updates", ">> Applied {} {}. {}", result.updated, result.updated == 1 ? "query" : "queries", summary);
LOG_INFO("sql.updates", " ");
return true;
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string> const* setDirectories)
bool UpdateDatabase(DatabaseUpdatePool& pool, DBUpdaterInfo const& info,
std::vector<std::string> const* setDirectories)
{
if (!DBUpdaterUtil::CheckExecutable())
{
return false;
}
Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());
Path const sourceDirectory(info.sourceDirectory);
if (!is_directory(sourceDirectory))
{
return false;
@@ -327,13 +357,13 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string>
auto CheckUpdateTable = [&](std::string const& tableName)
{
auto checkTable = DBUpdater<T>::Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
auto checkTable = Retrieve(pool, Acore::StringFormat("SHOW TABLES LIKE '{}'", tableName));
if (!checkTable)
{
Path const temp(GetBaseFilesDirectory() + tableName + ".sql");
Path const temp = Path(info.baseFilesDirectory) / (tableName + ".sql");
try
{
DBUpdater<T>::ApplyFile(pool, temp);
ApplyFile(pool, temp);
}
catch (UpdateException&)
{
@@ -351,9 +381,9 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string>
return false;
}
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { DBUpdater<T>::Apply(pool, query); },
[&](Path const & file) { DBUpdater<T>::ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); }, DBUpdater<T>::GetDBModuleName(), setDirectories);
UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const & query) { Apply(pool, query); },
[&](Path const & file) { ApplyFile(pool, file); },
[&](std::string const & query) -> QueryResult { return Retrieve(pool, query); }, info.dbModuleName, setDirectories);
UpdateResult result;
try
@@ -372,8 +402,7 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string>
return true;
}
template<class T>
bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
bool PopulateDatabase(DatabaseUpdatePool& pool, DBUpdaterInfo const& info)
{
{
QueryResult const result = Retrieve(pool, "SHOW TABLES");
@@ -384,9 +413,9 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
if (!DBUpdaterUtil::CheckExecutable())
return false;
LOG_INFO("sql.updates", "Database {} is empty, auto populating it...", DBUpdater<T>::GetTableName());
LOG_INFO("sql.updates", "Database {} is empty, auto populating it...", info.displayName);
std::string const DirPathStr = DBUpdater<T>::GetBaseFilesDirectory();
std::string const DirPathStr = info.baseFilesDirectory;
Path const DirPath(DirPathStr);
if (!std::filesystem::is_directory(DirPath))
@@ -445,28 +474,26 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
return true;
}
template<class T>
QueryResult DBUpdater<T>::Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query)
QueryResult Retrieve(DatabaseUpdatePool& pool, std::string const& query)
{
return pool.Query(query.c_str());
return pool.Query(query);
}
template<class T>
void DBUpdater<T>::Apply(DatabaseWorkerPool<T>& pool, std::string const& query)
void Apply(DatabaseUpdatePool& pool, std::string const& query)
{
pool.DirectExecute(query.c_str());
pool.DirectExecute(query);
}
template<class T>
void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path)
void ApplyFile(DatabaseUpdatePool& pool, Path const& path)
{
DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password,
pool.GetConnectionInfo()->port_or_socket, pool.GetConnectionInfo()->database, pool.GetConnectionInfo()->ssl, path);
ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user,
pool.GetConnectionInfo()->password, pool.GetConnectionInfo()->port_or_socket,
pool.GetConnectionInfo()->database, pool.GetConnectionInfo()->ssl, path);
}
template<class T>
void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database, std::string const& ssl, Path const& path)
void ApplyFile(DatabaseUpdatePool& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database,
std::string const& ssl, Path const& path)
{
std::string configTempDir = sConfigMgr->GetOption<std::string>("TempDir", "");
@@ -554,6 +581,58 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos
}
}
} // anonymous namespace
template<class T>
DBUpdaterInfo DBUpdater<T>::GetUpdaterInfo()
{
return { GetTableName(), GetSourceDirectory(), GetBaseFilesDirectory(), GetDBModuleName() };
}
template<class T>
bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)
{
DatabaseWorkerPoolAdapter<T> adapter(pool);
return CreateDatabase(adapter);
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::string_view modulesList /*= {}*/)
{
DatabaseWorkerPoolAdapter<T> adapter(pool);
return UpdateDatabase(adapter, GetUpdaterInfo(), modulesList);
}
template<class T>
bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool, std::vector<std::string> const* setDirectories)
{
DatabaseWorkerPoolAdapter<T> adapter(pool);
return UpdateDatabase(adapter, GetUpdaterInfo(), setDirectories);
}
template<class T>
bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
{
DatabaseWorkerPoolAdapter<T> adapter(pool);
return PopulateDatabase(adapter, GetUpdaterInfo());
}
bool ModuleDBUpdater::Create(DatabaseUpdatePool& pool)
{
return CreateDatabase(pool);
}
bool ModuleDBUpdater::Update(DatabaseUpdatePool& pool, DBUpdaterInfo const& info,
std::string_view modulesList /*= {}*/)
{
return UpdateDatabase(pool, info, modulesList);
}
bool ModuleDBUpdater::Populate(DatabaseUpdatePool& pool, DBUpdaterInfo const& info)
{
return PopulateDatabase(pool, info);
}
template class AC_DATABASE_API DBUpdater<LoginDatabaseConnection>;
template class AC_DATABASE_API DBUpdater<WorldDatabaseConnection>;
template class AC_DATABASE_API DBUpdater<CharacterDatabaseConnection>;
+25 -8
View File
@@ -19,13 +19,13 @@
#define DBUpdater_h__
#include "DatabaseEnv.h"
#include "DatabaseUpdatePool.h"
#include "Define.h"
#include "QueryResult.h"
#include <filesystem>
#include <string>
template <class T>
class DatabaseWorkerPool;
#include <string_view>
#include <vector>
namespace boost
{
@@ -71,6 +71,15 @@ private:
static uint32& failed_updates();
};
// Runtime metadata the updater needs about one database, core or module owned.
struct DBUpdaterInfo
{
std::string displayName; // name used in log output
std::string sourceDirectory; // root directory holding the sql tree
std::string baseFilesDirectory; // base *.sql files, trailing separator optional
std::string dbModuleName; // update-fetcher module name, must be lowercase
};
template <class T>
class AC_DATABASE_API DBUpdater
{
@@ -79,6 +88,7 @@ public:
static inline std::string GetConfigEntry();
static inline std::string GetTableName();
static std::string GetSourceDirectory();
static std::string GetBaseFilesDirectory();
static bool IsEnabled(uint32 const updateMask);
static BaseLocation GetBaseLocationType();
@@ -91,11 +101,18 @@ public:
static std::string GetDBModuleName();
private:
static QueryResult Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query);
static void Apply(DatabaseWorkerPool<T>& pool, std::string const& query);
static void ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path);
static void ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user,
std::string const& password, std::string const& port_or_socket, std::string const& database, std::string const& ssl, Path const& path);
static DBUpdaterInfo GetUpdaterInfo();
};
// Non-template updater entry points for module-owned pools (see ModuleDatabasePool).
// Mirrors the DBUpdater<T> flow: Create the schema when missing, Populate an empty
// database from the base files, then apply pending updates through the UpdateFetcher.
class AC_DATABASE_API ModuleDBUpdater
{
public:
static bool Create(DatabaseUpdatePool& pool);
static bool Update(DatabaseUpdatePool& pool, DBUpdaterInfo const& info, std::string_view modulesList = {});
static bool Populate(DatabaseUpdatePool& pool, DBUpdaterInfo const& info);
};
#endif // DBUpdater_h__
@@ -19,6 +19,11 @@
#include "ScriptMgr.h"
#include "ScriptMgrMacros.h"
bool ScriptMgr::OnModuleDatabasesLoading()
{
CALL_ENABLED_BOOLEAN_HOOKS(DatabaseScript, DATABASEHOOK_ON_MODULE_DATABASES_LOADING, !script->OnModuleDatabasesLoading());
}
void ScriptMgr::OnAfterDatabasesLoaded(uint32 updateFlags)
{
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_AFTER_DATABASES_LOADED, script->OnAfterDatabasesLoaded(updateFlags));
@@ -29,6 +34,26 @@ void ScriptMgr::OnAfterDatabaseLoadCreatureTemplates(std::vector<CreatureTemplat
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_AFTER_DATABASE_LOAD_CREATURETEMPLATES, script->OnAfterDatabaseLoadCreatureTemplates(creatureTemplates));
}
void ScriptMgr::OnModuleDatabasesKeepAlive()
{
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_MODULE_DATABASES_KEEPALIVE, script->OnModuleDatabasesKeepAlive());
}
void ScriptMgr::OnModuleDatabasesClosing()
{
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_MODULE_DATABASES_CLOSING, script->OnModuleDatabasesClosing());
}
void ScriptMgr::OnDatabaseWarnAboutSyncQueries(bool apply)
{
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_DATABASE_WARN_ABOUT_SYNC_QUERIES, script->OnDatabaseWarnAboutSyncQueries(apply));
}
void ScriptMgr::OnDatabaseGetDBRevision(std::map<std::string, std::string>& revisions)
{
CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_DATABASE_GET_DB_REVISION, script->OnDatabaseGetDBRevision(revisions));
}
DatabaseScript::DatabaseScript(char const* name, std::vector<uint16> enabledHooks)
: ScriptObject(name, DATABASEHOOK_END)
{
@@ -19,12 +19,19 @@
#define SCRIPT_OBJECT_DATABASE_SCRIPT_H_
#include "ScriptObject.h"
#include <map>
#include <string>
#include <vector>
enum DatabaseHook
{
DATABASEHOOK_ON_AFTER_DATABASES_LOADED,
DATABASEHOOK_ON_AFTER_DATABASE_LOAD_CREATURETEMPLATES,
DATABASEHOOK_ON_MODULE_DATABASES_LOADING,
DATABASEHOOK_ON_MODULE_DATABASES_KEEPALIVE,
DATABASEHOOK_ON_MODULE_DATABASES_CLOSING,
DATABASEHOOK_ON_DATABASE_WARN_ABOUT_SYNC_QUERIES,
DATABASEHOOK_ON_DATABASE_GET_DB_REVISION,
DATABASEHOOK_END
};
@@ -52,6 +59,38 @@ public:
*/
virtual void OnAfterDatabaseLoadCreatureTemplates(std::vector<CreatureTemplate*> /*creatureTemplates*/) { }
/**
* @brief Called once the core databases are up, so a module can open a database of its own.
* Runs before the rest of the world loads, unlike OnAfterDatabasesLoaded which reports the
* finished core load.
*
* @return false to abort startup, e.g. when the module's own database failed to open
*/
[[nodiscard]] virtual bool OnModuleDatabasesLoading() { return true; }
/**
* @brief Called on the world's keep-alive tick, alongside the core pools being pinged.
*/
virtual void OnModuleDatabasesKeepAlive() { }
/**
* @brief Called after the core databases are closed, so a module can close its own.
*/
virtual void OnModuleDatabasesClosing() { }
/**
* @brief Called when the core turns its synchronous-query warning on or off.
*
* @param apply True when the warning is being enabled
*/
virtual void OnDatabaseWarnAboutSyncQueries(bool /*apply*/) { }
/**
* @brief Called by .server info to collect the revision of a module-owned database.
*
* @param revisions Revision string to report, keyed by module name
*/
virtual void OnDatabaseGetDBRevision(std::map<std::string, std::string>& /*revisions*/) { }
};
#endif
+5
View File
@@ -712,8 +712,13 @@ public: /* CommandSC */
public: /* DatabaseScript */
bool OnModuleDatabasesLoading();
void OnAfterDatabasesLoaded(uint32 updateFlags);
void OnAfterDatabaseLoadCreatureTemplates(std::vector<CreatureTemplate*> creatureTemplateStore);
void OnModuleDatabasesKeepAlive();
void OnModuleDatabasesClosing();
void OnDatabaseWarnAboutSyncQueries(bool apply);
void OnDatabaseGetDBRevision(std::map<std::string, std::string>& revisions);
public: /* WorldObjectScript */
+1
View File
@@ -1322,6 +1322,7 @@ void World::Update(uint32 diff)
CharacterDatabase.KeepAlive();
LoginDatabase.KeepAlive();
WorldDatabase.KeepAlive();
sScriptMgr->OnModuleDatabasesKeepAlive();
}
{
@@ -27,6 +27,7 @@
#include "MySQLThreading.h"
#include "RBAC.h"
#include "Realm.h"
#include "ScriptMgr.h"
#include "StringConvert.h"
#include "UpdateTime.h"
#include "VMapFactory.h"
@@ -34,6 +35,7 @@
#include "WorldSessionMgr.h"
#include <boost/version.hpp>
#include <filesystem>
#include <map>
#include <numeric>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
@@ -216,6 +218,11 @@ public:
handler->PSendSysMessage("Using World DB: {}", sWorld->GetDBVersion());
std::map<std::string, std::string> moduleDBRevisions;
sScriptMgr->OnDatabaseGetDBRevision(moduleDBRevisions);
for (auto const& [moduleName, revision] : moduleDBRevisions)
handler->PSendSysMessage("Using {} DB Revision: {}", moduleName, revision);
std::string lldb = "No updates found!";
if (QueryResult resL = LoginDatabase.Query("SELECT name FROM updates ORDER BY name DESC LIMIT 1"))
{