diff options
Diffstat (limited to 'src/server/database')
32 files changed, 844 insertions, 605 deletions
diff --git a/src/server/database/CMakeLists.txt b/src/server/database/CMakeLists.txt index 5a53899f4cb..bd2fa280ad6 100644 --- a/src/server/database/CMakeLists.txt +++ b/src/server/database/CMakeLists.txt @@ -8,34 +8,22 @@  # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -if (NOT MYSQL_FOUND) -  message(SEND_ERROR "MySQL wasn't found on your system but it's required to build the servers!") -endif() - -if( USE_COREPCH ) -  include_directories(${CMAKE_CURRENT_BINARY_DIR}) -endif() - -file(GLOB_RECURSE sources_Database Database/*.cpp Database/*.h) -file(GLOB_RECURSE sources_Logging Logging/*.cpp Logging/*.h) -file(GLOB_RECURSE sources_Updater Updater/*.cpp Updater/*.h) - -file(GLOB sources_localdir *.cpp *.h) - -# -# Build shared sourcelist -# +CollectSourceFiles( +  ${CMAKE_CURRENT_SOURCE_DIR} +  PRIVATE_SOURCES +  # Exclude +  ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)  if (USE_COREPCH) -  set(database_STAT_PCH_HDR PrecompiledHeaders/databasePCH.h) -  set(database_STAT_PCH_SRC PrecompiledHeaders/databasePCH.cpp) +  set(PRIVATE_PCH_HEADER PrecompiledHeaders/databasePCH.h) +  set(PRIVATE_PCH_SOURCE PrecompiledHeaders/databasePCH.cpp)  endif() -set(database_STAT_SRCS -  ${database_STAT_SRCS} -  ${sources_Database} -  ${sources_Logging} -  ${sources_Updater} +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(database +  ${PRIVATE_PCH_SOURCE} +  ${PRIVATE_SOURCES}  )  # Do NOT add any extra include directory unless it does not create unneeded extra dependencies, @@ -47,31 +35,43 @@ set(database_STAT_SRCS  # linkage (enums, defines...) it is discouraged to do so unless necessary, as it will pullute  # include_directories leading to further unnoticed dependency aditions  # Linker Depencency requirements: common -include_directories( +CollectIncludeDirectories(    ${CMAKE_CURRENT_SOURCE_DIR} -  ${CMAKE_CURRENT_SOURCE_DIR}/Database -  ${CMAKE_CURRENT_SOURCE_DIR}/Updater -  ${CMAKE_SOURCE_DIR}/dep/cppformat -  ${CMAKE_SOURCE_DIR}/dep/process -  ${CMAKE_SOURCE_DIR}/src/common/ -  ${CMAKE_SOURCE_DIR}/src/common/Configuration -  ${CMAKE_SOURCE_DIR}/src/common/Debugging -  ${CMAKE_SOURCE_DIR}/src/common/Logging -  ${CMAKE_SOURCE_DIR}/src/common/Threading -  ${CMAKE_SOURCE_DIR}/src/common/Utilities -  ${MYSQL_INCLUDE_DIR} -  ${OPENSSL_INCLUDE_DIR} -  ${VALGRIND_INCLUDE_DIR} -) +  PUBLIC_INCLUDES +  # Exclude +  ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders) -GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(database +  PUBLIC +    ${PUBLIC_INCLUDES} +  PRIVATE +    ${CMAKE_CURRENT_BINARY_DIR}) -add_library(database STATIC -  ${database_STAT_SRCS} -  ${database_STAT_PCH_SRC} -) +add_definitions(-DTRINITY_API_EXPORT_DATABASE) + +target_link_libraries(database +  PUBLIC +    common +    mysql) + +set_target_properties(database +    PROPERTIES +      FOLDER +        "server") + +if( BUILD_SHARED_LIBS ) +  if( UNIX ) +    install(TARGETS database +      LIBRARY +        DESTINATION lib) +  elseif( WIN32 ) +    install(TARGETS database +      RUNTIME +        DESTINATION "${CMAKE_INSTALL_PREFIX}") +  endif() +endif()  # Generate precompiled header  if (USE_COREPCH) -  add_cxx_pch(database ${database_STAT_PCH_HDR} ${database_STAT_PCH_SRC}) +  add_cxx_pch(database ${PRIVATE_PCH_HEADER} ${PRIVATE_PCH_SOURCE})  endif () diff --git a/src/server/database/Database/AdhocStatement.h b/src/server/database/Database/AdhocStatement.h index ab85493a14e..9315038bce1 100644 --- a/src/server/database/Database/AdhocStatement.h +++ b/src/server/database/Database/AdhocStatement.h @@ -25,7 +25,7 @@ typedef std::future<QueryResult> QueryResultFuture;  typedef std::promise<QueryResult> QueryResultPromise;  /*! Raw, ad-hoc query. */ -class BasicStatementTask : public SQLOperation +class TC_DATABASE_API BasicStatementTask : public SQLOperation  {      public:          BasicStatementTask(const char* sql, bool async = false); diff --git a/src/server/database/Database/DatabaseEnv.cpp b/src/server/database/Database/DatabaseEnv.cpp new file mode 100644 index 00000000000..3b2e632e4fb --- /dev/null +++ b/src/server/database/Database/DatabaseEnv.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * 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 "DatabaseEnv.h" + +WorldDatabaseWorkerPool WorldDatabase; +CharacterDatabaseWorkerPool CharacterDatabase; +LoginDatabaseWorkerPool LoginDatabase; diff --git a/src/server/database/Database/DatabaseEnv.h b/src/server/database/Database/DatabaseEnv.h index cc8355a1302..05a4d5aad75 100644 --- a/src/server/database/Database/DatabaseEnv.h +++ b/src/server/database/Database/DatabaseEnv.h @@ -38,9 +38,11 @@  #include "Implementation/CharacterDatabase.h"  #include "Implementation/WorldDatabase.h" -extern WorldDatabaseWorkerPool WorldDatabase; -extern CharacterDatabaseWorkerPool CharacterDatabase; -extern LoginDatabaseWorkerPool LoginDatabase; +/// Accessor to the world database +TC_DATABASE_API extern WorldDatabaseWorkerPool WorldDatabase; +/// Accessor to the character database +TC_DATABASE_API extern CharacterDatabaseWorkerPool CharacterDatabase; +/// Accessor to the realm/login database +TC_DATABASE_API extern LoginDatabaseWorkerPool LoginDatabase;  #endif - diff --git a/src/server/database/Database/DatabaseLoader.cpp b/src/server/database/Database/DatabaseLoader.cpp index 92d8730cd12..f358bdd54b4 100644 --- a/src/server/database/Database/DatabaseLoader.cpp +++ b/src/server/database/Database/DatabaseLoader.cpp @@ -32,7 +32,7 @@ DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool<T>& pool, std::st  {      bool const updatesEnabledForThis = DBUpdater<T>::IsEnabled(_updateFlags); -    _open.push(std::make_pair([this, name, updatesEnabledForThis, &pool]() -> bool +    _open.push([this, name, updatesEnabledForThis, &pool]() -> bool      {          std::string const dbString = sConfigMgr->GetStringDefault(name + "DatabaseInfo", "");          if (dbString.empty()) @@ -71,12 +71,13 @@ DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool<T>& pool, std::st                  return false;              }          } +        // Add the close operation +        _close.push([&pool] +        { +            pool.Close(); +        });          return true; -    }, -    [&pool]() -    { -        pool.Close(); -    })); +    });      // Populate and update only if updates are enabled for this pool      if (updatesEnabledForThis) @@ -137,38 +138,7 @@ bool DatabaseLoader::Load()  bool DatabaseLoader::OpenDatabases()  { -    while (!_open.empty()) -    { -        std::pair<Predicate, std::function<void()>> const load = _open.top(); -        if (load.first()) -            _close.push(load.second); -        else -        { -            // Close all loaded databases -            while (!_close.empty()) -            { -                _close.top()(); -                _close.pop(); -            } -            return false; -        } - -        _open.pop(); -    } -    return true; -} - -// Processes the elements of the given stack until a predicate returned false. -bool DatabaseLoader::Process(std::stack<Predicate>& stack) -{ -    while (!stack.empty()) -    { -        if (!stack.top()()) -            return false; - -        stack.pop(); -    } -    return true; +    return Process(_open);  }  bool DatabaseLoader::PopulateDatabases() @@ -186,9 +156,30 @@ bool DatabaseLoader::PrepareStatements()      return Process(_prepare);  } -template -DatabaseLoader& DatabaseLoader::AddDatabase<LoginDatabaseConnection>(DatabaseWorkerPool<LoginDatabaseConnection>& pool, std::string const& name); -template -DatabaseLoader& DatabaseLoader::AddDatabase<WorldDatabaseConnection>(DatabaseWorkerPool<WorldDatabaseConnection>& pool, std::string const& name); -template -DatabaseLoader& DatabaseLoader::AddDatabase<CharacterDatabaseConnection>(DatabaseWorkerPool<CharacterDatabaseConnection>& pool, std::string const& name); +bool DatabaseLoader::Process(std::queue<Predicate>& queue) +{ +    while (!queue.empty()) +    { +        if (!queue.front()()) +        { +            // Close all open databases which have a registered close operation +            while (!_close.empty()) +            { +                _close.top()(); +                _close.pop(); +            } + +            return false; +        } + +        queue.pop(); +    } +    return true; +} + +template TC_DATABASE_API +DatabaseLoader& DatabaseLoader::AddDatabase<LoginDatabaseConnection>(LoginDatabaseWorkerPool&, std::string const&); +template TC_DATABASE_API +DatabaseLoader& DatabaseLoader::AddDatabase<CharacterDatabaseConnection>(CharacterDatabaseWorkerPool&, std::string const&); +template TC_DATABASE_API +DatabaseLoader& DatabaseLoader::AddDatabase<WorldDatabaseConnection>(WorldDatabaseWorkerPool&, std::string const&); diff --git a/src/server/database/Database/DatabaseLoader.h b/src/server/database/Database/DatabaseLoader.h index da92cf85a9f..647c1e113e3 100644 --- a/src/server/database/Database/DatabaseLoader.h +++ b/src/server/database/Database/DatabaseLoader.h @@ -19,14 +19,15 @@  #define DatabaseLoader_h__  #include "DatabaseWorkerPool.h" -#include "DatabaseEnv.h" +#include "DBUpdater.h" -#include <stack>  #include <functional> +#include <stack> +#include <queue>  // A helper class to initiate all database worker pools,  // handles updating, delays preparing of statements and cleans up on failure. -class DatabaseLoader +class TC_DATABASE_API DatabaseLoader  {  public:      DatabaseLoader(std::string const& logger, uint32 const defaultUpdateMask); @@ -56,16 +57,18 @@ private:      bool PrepareStatements();      using Predicate = std::function<bool()>; +    using Closer = std::function<void()>; -    static bool Process(std::stack<Predicate>& stack); +    // Invokes all functions in the given queue and closes the databases on errors. +    // Returns false when there was an error. +    bool Process(std::queue<Predicate>& queue);      std::string const _logger;      bool const _autoSetup;      uint32 const _updateFlags; -    std::stack<std::pair<Predicate, std::function<void()>>> _open; -    std::stack<std::function<void()>> _close; -    std::stack<Predicate> _populate, _update, _prepare; +    std::queue<Predicate> _open, _populate, _update, _prepare; +    std::stack<Closer> _close;  };  #endif // DatabaseLoader_h__ diff --git a/src/server/database/Database/DatabaseWorker.h b/src/server/database/Database/DatabaseWorker.h index c21a3d2a343..d6b43943f7d 100644 --- a/src/server/database/Database/DatabaseWorker.h +++ b/src/server/database/Database/DatabaseWorker.h @@ -24,7 +24,7 @@  class MySQLConnection;  class SQLOperation; -class DatabaseWorker +class TC_DATABASE_API DatabaseWorker  {      public:          DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection); diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp new file mode 100644 index 00000000000..6fc4dc21a51 --- /dev/null +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * 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 "DatabaseWorkerPool.h" +#include "DatabaseEnv.h" + +#define MIN_MYSQL_SERVER_VERSION 50100u +#define MIN_MYSQL_CLIENT_VERSION 50100u + +template <class T> +DatabaseWorkerPool<T>::DatabaseWorkerPool() +    : _queue(new ProducerConsumerQueue<SQLOperation*>()), +      _async_threads(0), _synch_threads(0) +{ +    WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe."); +    WPFatal(mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION, "TrinityCore does not support MySQL versions below 5.1"); +    WPFatal(mysql_get_client_version() == MYSQL_VERSION_ID, "Used MySQL library version (%s) does not match the version used to compile TrinityCore (%s). Search on forum for TCE00011.", +        mysql_get_client_info(), MYSQL_SERVER_VERSION); +} + +template <class T> +void DatabaseWorkerPool<T>::SetConnectionInfo(std::string const& infoString, +    uint8 const asyncThreads, uint8 const synchThreads) +{ +    _connectionInfo = Trinity::make_unique<MySQLConnectionInfo>(infoString); + +    _async_threads = asyncThreads; +    _synch_threads = synchThreads; +} + +template <class T> +uint32 DatabaseWorkerPool<T>::Open() +{ +    WPFatal(_connectionInfo.get(), "Connection info was not set!"); + +    TC_LOG_INFO("sql.driver", "Opening DatabasePool '%s'. " +        "Asynchronous connections: %u, synchronous connections: %u.", +        GetDatabaseName(), _async_threads, _synch_threads); + +    uint32 error = OpenConnections(IDX_ASYNC, _async_threads); + +    if (error) +        return error; + +    error = OpenConnections(IDX_SYNCH, _synch_threads); + +    if (!error) +    { +        TC_LOG_INFO("sql.driver", "DatabasePool '%s' opened successfully. " SZFMTD +                    " total connections running.", GetDatabaseName(), +                    (_connections[IDX_SYNCH].size() + _connections[IDX_ASYNC].size())); +    } + +    return error; +} + +template <class T> +void DatabaseWorkerPool<T>::Close() +{ +    TC_LOG_INFO("sql.driver", "Closing down DatabasePool '%s'.", GetDatabaseName()); + +    //! Closes the actualy MySQL connection. +    _connections[IDX_ASYNC].clear(); + +    TC_LOG_INFO("sql.driver", "Asynchronous connections on DatabasePool '%s' terminated. " +                "Proceeding with synchronous connections.", +        GetDatabaseName()); + +    //! Shut down the synchronous connections +    //! There's no need for locking the connection, because DatabaseWorkerPool<>::Close +    //! should only be called after any other thread tasks in the core have exited, +    //! meaning there can be no concurrent access at this point. +    _connections[IDX_SYNCH].clear(); + +    TC_LOG_INFO("sql.driver", "All connections on DatabasePool '%s' closed.", GetDatabaseName()); +} + +template <class T> +bool DatabaseWorkerPool<T>::PrepareStatements() +{ +    for (auto& connections : _connections) +        for (auto& connection : connections) +        { +            connection->LockIfReady(); +            if (!connection->PrepareStatements()) +            { +                connection->Unlock(); +                Close(); +                return false; +            } +            else +                connection->Unlock(); +        } + +    return true; +} + +template <class T> +QueryResult DatabaseWorkerPool<T>::Query(const char* sql, T* connection /*= nullptr*/) +{ +    if (!connection) +        connection = GetFreeConnection(); + +    ResultSet* result = connection->Query(sql); +    connection->Unlock(); +    if (!result || !result->GetRowCount() || !result->NextRow()) +    { +        delete result; +        return QueryResult(NULL); +    } + +    return QueryResult(result); +} + +template <class T> +PreparedQueryResult DatabaseWorkerPool<T>::Query(PreparedStatement* stmt) +{ +    auto connection = GetFreeConnection(); +    PreparedResultSet* ret = connection->Query(stmt); +    connection->Unlock(); + +    //! Delete proxy-class. Not needed anymore +    delete stmt; + +    if (!ret || !ret->GetRowCount()) +    { +        delete ret; +        return PreparedQueryResult(NULL); +    } + +    return PreparedQueryResult(ret); +} + +template <class T> +QueryResultFuture DatabaseWorkerPool<T>::AsyncQuery(const char* sql) +{ +    BasicStatementTask* task = new BasicStatementTask(sql, true); +    // Store future result before enqueueing - task might get already processed and deleted before returning from this method +    QueryResultFuture result = task->GetFuture(); +    Enqueue(task); +    return result; +} + +template <class T> +PreparedQueryResultFuture DatabaseWorkerPool<T>::AsyncQuery(PreparedStatement* stmt) +{ +    PreparedStatementTask* task = new PreparedStatementTask(stmt, true); +    // Store future result before enqueueing - task might get already processed and deleted before returning from this method +    PreparedQueryResultFuture result = task->GetFuture(); +    Enqueue(task); +    return result; +} + +template <class T> +QueryResultHolderFuture DatabaseWorkerPool<T>::DelayQueryHolder(SQLQueryHolder* holder) +{ +    SQLQueryHolderTask* task = new SQLQueryHolderTask(holder); +    // Store future result before enqueueing - task might get already processed and deleted before returning from this method +    QueryResultHolderFuture result = task->GetFuture(); +    Enqueue(task); +    return result; +} + +template <class T> +void DatabaseWorkerPool<T>::CommitTransaction(SQLTransaction transaction) +{ +#ifdef TRINITY_DEBUG +    //! Only analyze transaction weaknesses in Debug mode. +    //! Ideally we catch the faults in Debug mode and then correct them, +    //! so there's no need to waste these CPU cycles in Release mode. +    switch (transaction->GetSize()) +    { +    case 0: +        TC_LOG_DEBUG("sql.driver", "Transaction contains 0 queries. Not executing."); +        return; +    case 1: +        TC_LOG_DEBUG("sql.driver", "Warning: Transaction only holds 1 query, consider removing Transaction context in code."); +        break; +    default: +        break; +    } +#endif // TRINITY_DEBUG + +    Enqueue(new TransactionTask(transaction)); +} + +template <class T> +void DatabaseWorkerPool<T>::DirectCommitTransaction(SQLTransaction& transaction) +{ +    T* connection = GetFreeConnection(); +    int errorCode = connection->ExecuteTransaction(transaction); +    if (!errorCode) +    { +        connection->Unlock();      // OK, operation succesful +        return; +    } + +    //! Handle MySQL Errno 1213 without extending deadlock to the core itself +    /// @todo More elegant way +    if (errorCode == ER_LOCK_DEADLOCK) +    { +        uint8 loopBreaker = 5; +        for (uint8 i = 0; i < loopBreaker; ++i) +        { +            if (!connection->ExecuteTransaction(transaction)) +                break; +        } +    } + +    //! Clean up now. +    transaction->Cleanup(); + +    connection->Unlock(); +} + +template <class T> +void DatabaseWorkerPool<T>::EscapeString(std::string& str) +{ +    if (str.empty()) +        return; + +    char* buf = new char[str.size() * 2 + 1]; +    EscapeString(buf, str.c_str(), uint32(str.size())); +    str = buf; +    delete[] buf; +} + +template <class T> +void DatabaseWorkerPool<T>::KeepAlive() +{ +    //! Ping synchronous connections +    for (auto& connection : _connections[IDX_SYNCH]) +    { +        if (connection->LockIfReady()) +        { +            connection->Ping(); +            connection->Unlock(); +        } +    } + +    //! Assuming all worker threads are free, every worker thread will receive 1 ping operation request +    //! If one or more worker threads are busy, the ping operations will not be split evenly, but this doesn't matter +    //! as the sole purpose is to prevent connections from idling. +    auto const count = _connections[IDX_ASYNC].size(); +    for (uint8 i = 0; i < count; ++i) +        Enqueue(new PingOperation); +} + +template <class T> +uint32 DatabaseWorkerPool<T>::OpenConnections(InternalIndex type, uint8 numConnections) +{ +    for (uint8 i = 0; i < numConnections; ++i) +    { +        // Create the connection +        auto connection = [&] { +            switch (type) +            { +            case IDX_ASYNC: +                return Trinity::make_unique<T>(_queue.get(), *_connectionInfo); +            case IDX_SYNCH: +                return Trinity::make_unique<T>(*_connectionInfo); +            default: +                ABORT(); +            } +        }(); + +        if (uint32 error = connection->Open()) +        { +            // Failed to open a connection or invalid version, abort and cleanup +            _connections[type].clear(); +            return error; +        } +        else if (mysql_get_server_version(connection->GetHandle()) < MIN_MYSQL_SERVER_VERSION) +        { +            TC_LOG_ERROR("sql.driver", "TrinityCore does not support MySQL versions below 5.1"); +            return 1; +        } +        else +        { +            _connections[type].push_back(std::move(connection)); +        } +    } + +    // Everything is fine +    return 0; +} + +template <class T> +T* DatabaseWorkerPool<T>::GetFreeConnection() +{ +    uint8 i = 0; +    auto const num_cons = _connections[IDX_SYNCH].size(); +    T* connection = nullptr; +    //! Block forever until a connection is free +    for (;;) +    { +        connection = _connections[IDX_SYNCH][++i % num_cons].get(); +        //! Must be matched with t->Unlock() or you will get deadlocks +        if (connection->LockIfReady()) +            break; +    } + +    return connection; +} + +template class TC_DATABASE_API DatabaseWorkerPool<LoginDatabaseConnection>; +template class TC_DATABASE_API DatabaseWorkerPool<WorldDatabaseConnection>; +template class TC_DATABASE_API DatabaseWorkerPool<CharacterDatabaseConnection>; diff --git a/src/server/database/Database/DatabaseWorkerPool.h b/src/server/database/Database/DatabaseWorkerPool.h index d5a254647eb..ffdde91c0a6 100644 --- a/src/server/database/Database/DatabaseWorkerPool.h +++ b/src/server/database/Database/DatabaseWorkerPool.h @@ -19,7 +19,7 @@  #define _DATABASEWORKERPOOL_H  #include "Common.h" -#include "Callback.h" +#include "QueryCallback.h"  #include "MySQLConnection.h"  #include "Transaction.h"  #include "DatabaseWorker.h" @@ -32,9 +32,7 @@  #include <mysqld_error.h>  #include <memory> - -#define MIN_MYSQL_SERVER_VERSION 50100u -#define MIN_MYSQL_CLIENT_VERSION 50100u +#include <array>  class PingOperation : public SQLOperation  { @@ -59,97 +57,21 @@ class DatabaseWorkerPool      public:          /* Activity state */ -        DatabaseWorkerPool() : _queue(new ProducerConsumerQueue<SQLOperation*>()), -            _async_threads(0), _synch_threads(0) -        { -            memset(_connectionCount, 0, sizeof(_connectionCount)); -            _connections.resize(IDX_SIZE); - -            WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe."); -            WPFatal(mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION, "TrinityCore does not support MySQL versions below 5.1"); -            WPFatal(mysql_get_client_version() == MYSQL_VERSION_ID, "Used MySQL library version (%s) does not match the version used to compile TrinityCore (%s).", -                mysql_get_client_info(), MYSQL_SERVER_VERSION); -        } +        DatabaseWorkerPool();          ~DatabaseWorkerPool()          {              _queue->Cancel();          } -        void SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads) -        { -            _connectionInfo.reset(new MySQLConnectionInfo(infoString)); - -            _async_threads = asyncThreads; -            _synch_threads = synchThreads; -        } +        void SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads); -        uint32 Open() -        { -            WPFatal(_connectionInfo.get(), "Connection info was not set!"); +        uint32 Open(); -            TC_LOG_INFO("sql.driver", "Opening DatabasePool '%s'. Asynchronous connections: %u, synchronous connections: %u.", -                GetDatabaseName(), _async_threads, _synch_threads); - -            uint32 error = OpenConnections(IDX_ASYNC, _async_threads); - -            if (error) -                return error; - -            error = OpenConnections(IDX_SYNCH, _synch_threads); - -            if (!error) -            { -                TC_LOG_INFO("sql.driver", "DatabasePool '%s' opened successfully. %u total connections running.", GetDatabaseName(), -                    (_connectionCount[IDX_SYNCH] + _connectionCount[IDX_ASYNC])); -            } - -            return error; -        } - -        void Close() -        { -            TC_LOG_INFO("sql.driver", "Closing down DatabasePool '%s'.", GetDatabaseName()); - -            for (uint8 i = 0; i < _connectionCount[IDX_ASYNC]; ++i) -            { -                T* t = _connections[IDX_ASYNC][i]; -                t->Close();         //! Closes the actualy MySQL connection. -            } - -            TC_LOG_INFO("sql.driver", "Asynchronous connections on DatabasePool '%s' terminated. Proceeding with synchronous connections.", -                GetDatabaseName()); - -            //! Shut down the synchronous connections -            //! There's no need for locking the connection, because DatabaseWorkerPool<>::Close -            //! should only be called after any other thread tasks in the core have exited, -            //! meaning there can be no concurrent access at this point. -            for (uint8 i = 0; i < _connectionCount[IDX_SYNCH]; ++i) -                _connections[IDX_SYNCH][i]->Close(); - -            TC_LOG_INFO("sql.driver", "All connections on DatabasePool '%s' closed.", GetDatabaseName()); -        } +        void Close();          //! Prepares all prepared statements -        bool PrepareStatements() -        { -            for (uint8 i = 0; i < IDX_SIZE; ++i) -                for (uint32 c = 0; c < _connectionCount[i]; ++c) -                { -                    T* t = _connections[i][c]; -                    t->LockIfReady(); -                    if (!t->PrepareStatements()) -                    { -                        t->Unlock(); -                        Close(); -                        return false; -                    } -                    else -                        t->Unlock(); -                } - -            return true; -        } +        bool PrepareStatements();          inline MySQLConnectionInfo const* GetConnectionInfo() const          { @@ -198,12 +120,12 @@ class DatabaseWorkerPool          //! This method should only be used for queries that are only executed once, e.g during startup.          void DirectExecute(const char* sql)          { -            if (!sql) +            if (Trinity::IsFormatEmptyOrNull(sql))                  return; -            T* t = GetFreeConnection(); -            t->Execute(sql); -            t->Unlock(); +            T* connection = GetFreeConnection(); +            connection->Execute(sql); +            connection->Unlock();          }          //! Directly executes a one-way SQL operation in string format -with variable args-, that will block the calling thread until finished. @@ -221,9 +143,9 @@ class DatabaseWorkerPool          //! Statement must be prepared with the CONNECTION_SYNCH flag.          void DirectExecute(PreparedStatement* stmt)          { -            T* t = GetFreeConnection(); -            t->Execute(stmt); -            t->Unlock(); +            T* connection = GetFreeConnection(); +            connection->Execute(stmt); +            connection->Unlock();              //! Delete proxy-class. Not needed anymore              delete stmt; @@ -235,21 +157,7 @@ class DatabaseWorkerPool          //! Directly executes an SQL query in string format that will block the calling thread until finished.          //! Returns reference counted auto pointer, no need for manual memory management in upper level code. -        QueryResult Query(const char* sql, T* conn = nullptr) -        { -            if (!conn) -                conn = GetFreeConnection(); - -            ResultSet* result = conn->Query(sql); -            conn->Unlock(); -            if (!result || !result->GetRowCount() || !result->NextRow()) -            { -                delete result; -                return QueryResult(NULL); -            } - -            return QueryResult(result); -        } +        QueryResult Query(const char* sql, T* connection = nullptr);          //! Directly executes an SQL query in string format -with variable args- that will block the calling thread until finished.          //! Returns reference counted auto pointer, no need for manual memory management in upper level code. @@ -267,7 +175,7 @@ class DatabaseWorkerPool          template<typename Format, typename... Args>          QueryResult PQuery(Format&& sql, Args&&... args)          { -            if (!sql) +            if (Trinity::IsFormatEmptyOrNull(sql))                  return QueryResult(nullptr);              return Query(Trinity::StringFormat(std::forward<Format>(sql), std::forward<Args>(args)...).c_str()); @@ -276,23 +184,7 @@ class DatabaseWorkerPool          //! Directly executes an SQL query in prepared format that will block the calling thread until finished.          //! Returns reference counted auto pointer, no need for manual memory management in upper level code.          //! Statement must be prepared with CONNECTION_SYNCH flag. -        PreparedQueryResult Query(PreparedStatement* stmt) -        { -            T* t = GetFreeConnection(); -            PreparedResultSet* ret = t->Query(stmt); -            t->Unlock(); - -            //! Delete proxy-class. Not needed anymore -            delete stmt; - -            if (!ret || !ret->GetRowCount()) -            { -                delete ret; -                return PreparedQueryResult(NULL); -            } - -            return PreparedQueryResult(ret); -        } +        PreparedQueryResult Query(PreparedStatement* stmt);          /**              Asynchronous query (with resultset) methods. @@ -300,14 +192,7 @@ class DatabaseWorkerPool          //! Enqueues a query in string format that will set the value of the QueryResultFuture return object as soon as the query is executed.          //! The return value is then processed in ProcessQueryCallback methods. -        QueryResultFuture AsyncQuery(const char* sql) -        { -            BasicStatementTask* task = new BasicStatementTask(sql, true); -            // Store future result before enqueueing - task might get already processed and deleted before returning from this method -            QueryResultFuture result = task->GetFuture(); -            Enqueue(task); -            return result; -        } +        QueryResultFuture AsyncQuery(const char* sql);          //! Enqueues a query in string format -with variable args- that will set the value of the QueryResultFuture return object as soon as the query is executed.          //! The return value is then processed in ProcessQueryCallback methods. @@ -320,27 +205,13 @@ class DatabaseWorkerPool          //! Enqueues a query in prepared format that will set the value of the PreparedQueryResultFuture return object as soon as the query is executed.          //! The return value is then processed in ProcessQueryCallback methods.          //! Statement must be prepared with CONNECTION_ASYNC flag. -        PreparedQueryResultFuture AsyncQuery(PreparedStatement* stmt) -        { -            PreparedStatementTask* task = new PreparedStatementTask(stmt, true); -            // Store future result before enqueueing - task might get already processed and deleted before returning from this method -            PreparedQueryResultFuture result = task->GetFuture(); -            Enqueue(task); -            return result; -        } +        PreparedQueryResultFuture AsyncQuery(PreparedStatement* stmt);          //! Enqueues a vector of SQL operations (can be both adhoc and prepared) that will set the value of the QueryResultHolderFuture          //! return object as soon as the query is executed.          //! The return value is then processed in ProcessQueryCallback methods.          //! Any prepared statements added to this holder need to be prepared with the CONNECTION_ASYNC flag. -        QueryResultHolderFuture DelayQueryHolder(SQLQueryHolder* holder) -        { -            SQLQueryHolderTask* task = new SQLQueryHolderTask(holder); -            // Store future result before enqueueing - task might get already processed and deleted before returning from this method -            QueryResultHolderFuture result = task->GetFuture(); -            Enqueue(task); -            return result; -        } +        QueryResultHolderFuture DelayQueryHolder(SQLQueryHolder* holder);          /**              Transaction context methods. @@ -354,57 +225,11 @@ class DatabaseWorkerPool          //! Enqueues a collection of one-way SQL operations (can be both adhoc and prepared). The order in which these operations          //! were appended to the transaction will be respected during execution. -        void CommitTransaction(SQLTransaction transaction) -        { -            #ifdef TRINITY_DEBUG -            //! Only analyze transaction weaknesses in Debug mode. -            //! Ideally we catch the faults in Debug mode and then correct them, -            //! so there's no need to waste these CPU cycles in Release mode. -            switch (transaction->GetSize()) -            { -                case 0: -                    TC_LOG_DEBUG("sql.driver", "Transaction contains 0 queries. Not executing."); -                    return; -                case 1: -                    TC_LOG_DEBUG("sql.driver", "Warning: Transaction only holds 1 query, consider removing Transaction context in code."); -                    break; -                default: -                    break; -            } -            #endif // TRINITY_DEBUG - -            Enqueue(new TransactionTask(transaction)); -        } +        void CommitTransaction(SQLTransaction transaction);          //! Directly executes a collection of one-way SQL operations (can be both adhoc and prepared). The order in which these operations          //! were appended to the transaction will be respected during execution. -        void DirectCommitTransaction(SQLTransaction& transaction) -        { -            T* con = GetFreeConnection(); -            int errorCode = con->ExecuteTransaction(transaction); -            if (!errorCode) -            { -                con->Unlock();      // OK, operation succesful -                return; -            } - -            //! Handle MySQL Errno 1213 without extending deadlock to the core itself -            /// @todo More elegant way -            if (errorCode == ER_LOCK_DEADLOCK) -            { -                uint8 loopBreaker = 5; -                for (uint8 i = 0; i < loopBreaker; ++i) -                { -                    if (!con->ExecuteTransaction(transaction)) -                        break; -                } -            } - -            //! Clean up now. -            transaction->Cleanup(); - -            con->Unlock(); -        } +        void DirectCommitTransaction(SQLTransaction& transaction);          //! Method used to execute prepared statements in a diverse context.          //! Will be wrapped in a transaction if valid object is present, otherwise executed standalone. @@ -441,90 +266,21 @@ class DatabaseWorkerPool          }          //! Apply escape string'ing for current collation. (utf8) -        void EscapeString(std::string& str) -        { -            if (str.empty()) -                return; - -            char* buf = new char[str.size() * 2 + 1]; -            EscapeString(buf, str.c_str(), str.size()); -            str = buf; -            delete[] buf; -        } +        void EscapeString(std::string& str);          //! Keeps all our MySQL connections alive, prevent the server from disconnecting us. -        void KeepAlive() -        { -            //! Ping synchronous connections -            for (uint8 i = 0; i < _connectionCount[IDX_SYNCH]; ++i) -            { -                T* t = _connections[IDX_SYNCH][i]; -                if (t->LockIfReady()) -                { -                    t->Ping(); -                    t->Unlock(); -                } -            } - -            //! Assuming all worker threads are free, every worker thread will receive 1 ping operation request -            //! If one or more worker threads are busy, the ping operations will not be split evenly, but this doesn't matter -            //! as the sole purpose is to prevent connections from idling. -            for (size_t i = 0; i < _connections[IDX_ASYNC].size(); ++i) -                Enqueue(new PingOperation); -        } +        void KeepAlive();      private: -        uint32 OpenConnections(InternalIndex type, uint8 numConnections) -        { -            _connections[type].resize(numConnections); -            for (uint8 i = 0; i < numConnections; ++i) -            { -                T* t; - -                if (type == IDX_ASYNC) -                    t = new T(_queue.get(), *_connectionInfo); -                else if (type == IDX_SYNCH) -                    t = new T(*_connectionInfo); -                else -                    ABORT(); - -                _connections[type][i] = t; -                ++_connectionCount[type]; - -                uint32 error = t->Open(); - -                if (!error) -                { -                    if (mysql_get_server_version(t->GetHandle()) < MIN_MYSQL_SERVER_VERSION) -                    { -                        TC_LOG_ERROR("sql.driver", "TrinityCore does not support MySQL versions below 5.1"); -                        error = 1; -                    } -                } - -                // Failed to open a connection or invalid version, abort and cleanup -                if (error) -                { -                    while (_connectionCount[type] != 0) -                    { -                        t = _connections[type][i--]; -                        delete t; -                        --_connectionCount[type]; -                    } -                    return error; -                } -            } - -            // Everything is fine -            return 0; -        } +        uint32 OpenConnections(InternalIndex type, uint8 numConnections);          unsigned long EscapeString(char *to, const char *from, unsigned long length)          {              if (!to || !from || !length)                  return 0; -            return mysql_real_escape_string(_connections[IDX_SYNCH][0]->GetHandle(), to, from, length); +            return mysql_real_escape_string( +                _connections[IDX_SYNCH].front()->GetHandle(), to, from, length);          }          void Enqueue(SQLOperation* op) @@ -534,22 +290,7 @@ class DatabaseWorkerPool          //! Gets a free connection in the synchronous connection pool.          //! Caller MUST call t->Unlock() after touching the MySQL context to prevent deadlocks. -        T* GetFreeConnection() -        { -            uint8 i = 0; -            size_t num_cons = _connectionCount[IDX_SYNCH]; -            T* t = NULL; -            //! Block forever until a connection is free -            for (;;) -            { -                t = _connections[IDX_SYNCH][++i % num_cons]; -                //! Must be matched with t->Unlock() or you will get deadlocks -                if (t->LockIfReady()) -                    break; -            } - -            return t; -        } +        T* GetFreeConnection();          char const* GetDatabaseName() const          { @@ -558,9 +299,7 @@ class DatabaseWorkerPool          //! Queue shared by async worker threads.          std::unique_ptr<ProducerConsumerQueue<SQLOperation*>> _queue; -        std::vector<std::vector<T*>> _connections; -        //! Counter of MySQL connections; -        uint32 _connectionCount[IDX_SIZE]; +        std::array<std::vector<std::unique_ptr<T>>, IDX_SIZE> _connections;          std::unique_ptr<MySQLConnectionInfo> _connectionInfo;          uint8 _async_threads, _synch_threads;  }; diff --git a/src/server/database/Database/Field.h b/src/server/database/Database/Field.h index ec9e626ee1b..123e25dbbf3 100644 --- a/src/server/database/Database/Field.h +++ b/src/server/database/Database/Field.h @@ -53,7 +53,7 @@      | SUM, AVG | DECIMAL           |      | COUNT    | BIGINT            |  */ -class Field +class TC_DATABASE_API Field  {      friend class ResultSet;      friend class PreparedResultSet; @@ -323,7 +323,7 @@ class Field              data.value = NULL;          } -        static size_t SizeForType(MYSQL_FIELD* field) +        static uint32 SizeForType(MYSQL_FIELD* field)          {              switch (field->type)              { diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index ab68aca2a8c..bb0e2bb09d4 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -42,11 +42,11 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_SEL_MAIL_LIST_INFO, "SELECT id, sender, (SELECT name FROM characters WHERE guid = sender) AS sendername, receiver, (SELECT name FROM characters WHERE guid = receiver) AS receivername, "                       "subject, deliver_time, expire_time, money, has_items FROM mail WHERE receiver = ? ", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_MAIL_LIST_ITEMS, "SELECT itemEntry,count FROM item_instance WHERE guid = ?", CONNECTION_SYNCH); -    PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.playerBytes, c.playerBytes2, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, " +    PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, "                       "gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid "                       "FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? LEFT JOIN guild_member AS gm ON c.guid = gm.guid "                       "LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL ORDER BY c.guid", CONNECTION_ASYNC); -    PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.playerBytes, c.playerBytes2, c.level, c.zone, c.map, " +    PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, "                       "c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, "                       "cb.guid, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.slot = ? "                       "LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid " @@ -56,7 +56,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_SEL_CHAR_RACE, "SELECT race FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHAR_LEVEL, "SELECT level FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHAR_ZONE, "SELECT zone FROM characters WHERE guid = ?", CONNECTION_SYNCH); -    PrepareStatement(CHAR_SEL_CHARACTER_NAME_DATA, "SELECT race, class, gender, level FROM characters WHERE guid = ?", CONNECTION_SYNCH); +    PrepareStatement(CHAR_SEL_CHARACTER_NAME_DATA, "SELECT race, class, gender, level, name FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHAR_POSITION_XYZ, "SELECT map, position_x, position_y, position_z FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHAR_POSITION, "SELECT position_x, position_y, position_z, orientation, map, taxi_path FROM characters WHERE guid = ?", CONNECTION_SYNCH); @@ -64,7 +64,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_DEL_BATTLEGROUND_RANDOM, "DELETE FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_INS_BATTLEGROUND_RANDOM, "INSERT INTO character_battleground_random (guid) VALUES (?)", CONNECTION_ASYNC); -    PrepareStatement(CHAR_SEL_CHARACTER, "SELECT guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags, " +    PrepareStatement(CHAR_SEL_CHARACTER, "SELECT guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, bankSlots, restState, playerFlags, "                       "position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "                       "resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, instance_mode_mask, "                       "arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, " @@ -157,11 +157,9 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_DEL_GIFT, "DELETE FROM character_gifts WHERE item_guid = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_SEL_CHARACTER_GIFT_BY_ITEM, "SELECT entry, flags FROM character_gifts WHERE item_guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_ACCOUNT_BY_NAME, "SELECT account FROM characters WHERE name = ?", CONNECTION_SYNCH); -    PrepareStatement(CHAR_SEL_CHARACTER_DATA_BY_GUID, "SELECT account, name, level FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES, "DELETE FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_INS_ACCOUNT_INSTANCE_LOCK_TIMES, "INSERT INTO account_instance_times (accountId, instanceId, releaseTime) VALUES (?, ?, ?)", CONNECTION_ASYNC);      PrepareStatement(CHAR_SEL_CHARACTER_NAME_CLASS, "SELECT name, class FROM characters WHERE guid = ?", CONNECTION_SYNCH); -    PrepareStatement(CHAR_SEL_CHARACTER_NAME, "SELECT name FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_MATCH_MAKER_RATING, "SELECT matchMakerRating FROM character_arena_stats WHERE guid = ? AND slot = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHARACTER_COUNT, "SELECT account, COUNT(guid) FROM characters WHERE account = ? GROUP BY account", CONNECTION_ASYNC);      PrepareStatement(CHAR_UPD_NAME, "UPDATE characters set name = ?, at_login = at_login & ~ ? WHERE guid = ?", CONNECTION_ASYNC); @@ -351,7 +349,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_DEL_LFG_DATA, "DELETE FROM lfg_data WHERE guid = ?", CONNECTION_ASYNC);      // Player saving -    PrepareStatement(CHAR_INS_CHARACTER, "INSERT INTO characters (guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags, " +    PrepareStatement(CHAR_INS_CHARACTER, "INSERT INTO characters (guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, bankSlots, restState, playerFlags, "                       "map, instance_id, instance_mode_mask, position_x, position_y, position_z, orientation, trans_x, trans_y, trans_z, trans_o, transguid, "                       "taximask, cinematic, "                       "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, " @@ -359,8 +357,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()                       "death_expire_time, taxi_path, arenaPoints, totalHonorPoints, todayHonorPoints, yesterdayHonorPoints, totalKills, "                       "todayKills, yesterdayKills, chosenTitle, knownCurrencies, watchedFaction, drunk, health, power1, power2, power3, "                       "power4, power5, power6, power7, latency, talentGroupsCount, activeTalentGroup, exploredZones, equipmentCache, ammoId, knownTitles, actionBars, grantableLevels) VALUES " -                     "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC); -    PrepareStatement(CHAR_UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,playerBytes=?,playerBytes2=?,playerFlags=?," +                     "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC); +    PrepareStatement(CHAR_UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,skin=?,face=?,hairStyle=?,hairColor=?,facialStyle=?,bankSlots=?,restState=?,playerFlags=?,"                       "map=?,instance_id=?,instance_mode_mask=?,position_x=?,position_y=?,position_z=?,orientation=?,trans_x=?,trans_y=?,trans_z=?,trans_o=?,transguid=?,taximask=?,cinematic=?,totaltime=?,leveltime=?,rest_bonus=?,"                       "logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,extra_flags=?,stable_slots=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?,"                       "arenaPoints=?,totalHonorPoints=?,todayHonorPoints=?,yesterdayHonorPoints=?,totalKills=?,todayKills=?,yesterdayKills=?,chosenTitle=?,knownCurrencies=?," @@ -407,7 +405,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extendState = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extendState) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); -    PrepareStatement(CHAR_UPD_GENDER_PLAYERBYTES, "UPDATE characters SET gender = ?, playerBytes = ?, playerBytes2 = ? WHERE guid = ?", CONNECTION_ASYNC); +    PrepareStatement(CHAR_UPD_GENDER_AND_APPEARANCE, "UPDATE characters SET gender = ?, skin = ?, face = ?, hairStyle = ?, hairColor = ?, facialStyle = ? WHERE guid = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags & ~ ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC); @@ -440,7 +438,6 @@ void CharacterDatabaseConnection::DoPrepareStatements()      PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID, "SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid = ? AND type = ? LIMIT 1", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? ORDER BY id DESC", CONNECTION_SYNCH); -    PrepareStatement(CHAR_SEL_CHAR_PLAYERBYTES2, "SELECT playerBytes2 FROM characters WHERE guid = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_SEL_CHAR_GUID_BY_NAME, "SELECT guid FROM characters WHERE name = ?", CONNECTION_SYNCH);      PrepareStatement(CHAR_DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?", CONNECTION_ASYNC);      PrepareStatement(CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 19d4a609e77..0cac6d35b55 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -146,13 +146,11 @@ enum CharacterDatabaseStatements      CHAR_DEL_ACCOUNT_INSTANCE_LOCK_TIMES,      CHAR_INS_ACCOUNT_INSTANCE_LOCK_TIMES,      CHAR_SEL_CHARACTER_NAME_CLASS, -    CHAR_SEL_CHARACTER_NAME,      CHAR_SEL_MATCH_MAKER_RATING,      CHAR_SEL_CHARACTER_COUNT,      CHAR_UPD_NAME,      CHAR_UPD_NAME_BY_GUID,      CHAR_DEL_DECLINED_NAME, -    CHAR_SEL_CHARACTER_DATA_BY_GUID,      CHAR_INS_GUILD,      CHAR_DEL_GUILD, @@ -334,7 +332,7 @@ enum CharacterDatabaseStatements      CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID,      CHAR_UPD_CHAR_INSTANCE,      CHAR_INS_CHAR_INSTANCE, -    CHAR_UPD_GENDER_PLAYERBYTES, +    CHAR_UPD_GENDER_AND_APPEARANCE,      CHAR_DEL_CHARACTER_SKILL,      CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS,      CHAR_UPD_REM_CHARACTER_SOCIAL_FLAGS, @@ -371,7 +369,6 @@ enum CharacterDatabaseStatements      CHAR_SEL_CHAR_OLD_CHARS,      CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID,      CHAR_SEL_MAIL, -    CHAR_SEL_CHAR_PLAYERBYTES2,      CHAR_SEL_CHAR_GUID_BY_NAME,      CHAR_DEL_CHAR_AURA_FROZEN,      CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, @@ -538,7 +535,7 @@ enum CharacterDatabaseStatements      MAX_CHARACTERDATABASE_STATEMENTS  }; -class CharacterDatabaseConnection : public MySQLConnection +class TC_DATABASE_API CharacterDatabaseConnection : public MySQLConnection  {  public:      typedef CharacterDatabaseStatements Statements; diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 66847f0a6a0..4f056e2686d 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -24,33 +24,34 @@ void LoginDatabaseConnection::DoPrepareStatements()      PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH);      PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC); -    PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_SYNCH); +    PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);      PrepareStatement(LOGIN_SEL_IP_INFO, "(SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?) "          "UNION "          "(SELECT NULL AS banned, country FROM ip2nation WHERE INET_NTOA(ip) = ?)", CONNECTION_ASYNC); -    PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')", CONNECTION_ASYNC);      PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate", CONNECTION_SYNCH); -    PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED, "SELECT bandate, unbandate FROM account_banned WHERE id = ? AND active = 1", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH);      PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban', 1)", CONNECTION_ASYNC);      PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC); -    PrepareStatement(LOGIN_SEL_SESSIONKEY, "SELECT a.sessionkey, a.id, aa.gmlevel  FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE username = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC);      PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); -    PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, a.lock_country, a.last_ip, aa.gmlevel, a.v, a.s, a.token_key FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH); +    PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " +        "ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s " +        "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); +    PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " +        "ab.unbandate = ab.bandate, aa.gmlevel, a.sessionKey " +        "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC);      PrepareStatement(LOGIN_SEL_LOGON_COUNTRY, "SELECT country FROM ip2nation WHERE ip < ? ORDER BY ip DESC LIMIT 0,1", CONNECTION_SYNCH);      PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC); -    PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, aa.gmLevel, "          "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) "          "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);      PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH); -    PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH); +    PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE  acctid = ?", CONNECTION_ASYNC);      PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH);      PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 69c2e758551..e206be16d73 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -33,25 +33,22 @@ enum LoginDatabaseStatements      LOGIN_DEL_EXPIRED_IP_BANS,      LOGIN_UPD_EXPIRED_ACCOUNT_BANS,      LOGIN_SEL_IP_INFO, -    LOGIN_SEL_IP_BANNED,      LOGIN_INS_IP_AUTO_BANNED, -    LOGIN_SEL_ACCOUNT_BANNED,      LOGIN_SEL_ACCOUNT_BANNED_ALL,      LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME,      LOGIN_INS_ACCOUNT_AUTO_BANNED,      LOGIN_DEL_ACCOUNT_BANNED, -    LOGIN_SEL_SESSIONKEY,      LOGIN_UPD_VS,      LOGIN_UPD_LOGONPROOF,      LOGIN_SEL_LOGONCHALLENGE, +    LOGIN_SEL_RECONNECTCHALLENGE,      LOGIN_SEL_LOGON_COUNTRY,      LOGIN_UPD_FAILEDLOGINS, -    LOGIN_SEL_FAILEDLOGINS,      LOGIN_SEL_ACCOUNT_ID_BY_NAME,      LOGIN_SEL_ACCOUNT_LIST_BY_NAME,      LOGIN_SEL_ACCOUNT_INFO_BY_NAME,      LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, -    LOGIN_SEL_NUM_CHARS_ON_REALM, +    LOGIN_SEL_REALM_CHARACTER_COUNTS,      LOGIN_SEL_ACCOUNT_BY_IP,      LOGIN_INS_IP_BANNED,      LOGIN_DEL_IP_NOT_BANNED, @@ -119,7 +116,7 @@ enum LoginDatabaseStatements      MAX_LOGINDATABASE_STATEMENTS  }; -class LoginDatabaseConnection : public MySQLConnection +class TC_DATABASE_API LoginDatabaseConnection : public MySQLConnection  {  public:      typedef LoginDatabaseStatements Statements; diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 7a183d5bf78..83720c1a996 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -30,8 +30,8 @@ void WorldDatabaseConnection::DoPrepareStatements()      PrepareStatement(WORLD_SEL_SMARTAI_WP, "SELECT entry, pointid, position_x, position_y, position_z FROM waypoints ORDER BY entry, pointid", CONNECTION_SYNCH);      PrepareStatement(WORLD_DEL_GAMEOBJECT, "DELETE FROM gameobject WHERE guid = ?", CONNECTION_ASYNC);      PrepareStatement(WORLD_DEL_EVENT_GAMEOBJECT, "DELETE FROM game_event_gameobject WHERE guid = ?", CONNECTION_ASYNC); -    PrepareStatement(WORLD_INS_GRAVEYARD_ZONE, "INSERT INTO game_graveyard_zone (id, ghost_zone, faction) VALUES (?, ?, ?)", CONNECTION_ASYNC); -    PrepareStatement(WORLD_DEL_GRAVEYARD_ZONE, "DELETE FROM game_graveyard_zone WHERE id = ? AND ghost_zone = ? AND faction = ?", CONNECTION_ASYNC); +    PrepareStatement(WORLD_INS_GRAVEYARD_ZONE, "INSERT INTO graveyard_zone (ID, GhostZone, Faction) VALUES (?, ?, ?)", CONNECTION_ASYNC); +    PrepareStatement(WORLD_DEL_GRAVEYARD_ZONE, "DELETE FROM graveyard_zone WHERE ID = ? AND GhostZone = ? AND Faction = ?", CONNECTION_ASYNC);      PrepareStatement(WORLD_INS_GAME_TELE, "INSERT INTO game_tele (id, position_x, position_y, position_z, orientation, map, name) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);      PrepareStatement(WORLD_DEL_GAME_TELE, "DELETE FROM game_tele WHERE name = ?", CONNECTION_ASYNC);      PrepareStatement(WORLD_INS_NPC_VENDOR, "INSERT INTO npc_vendor (entry, item, maxcount, incrtime, extendedcost) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h index 6ac4ce589e3..e0a02423446 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.h +++ b/src/server/database/Database/Implementation/WorldDatabase.h @@ -103,7 +103,7 @@ enum WorldDatabaseStatements      MAX_WORLDDATABASE_STATEMENTS  }; -class WorldDatabaseConnection : public MySQLConnection +class TC_DATABASE_API WorldDatabaseConnection : public MySQLConnection  {  public:      typedef WorldDatabaseStatements Statements; diff --git a/src/server/database/Database/MySQLConnection.cpp b/src/server/database/Database/MySQLConnection.cpp index 41dd61d3c3a..93d2a35f310 100644 --- a/src/server/database/Database/MySQLConnection.cpp +++ b/src/server/database/Database/MySQLConnection.cpp @@ -37,7 +37,6 @@ MySQLConnection::MySQLConnection(MySQLConnectionInfo& connInfo) :  m_reconnecting(false),  m_prepareError(false),  m_queue(NULL), -m_worker(NULL),  m_Mysql(NULL),  m_connectionInfo(connInfo),  m_connectionFlags(CONNECTION_SYNCH) { } @@ -50,24 +49,26 @@ m_Mysql(NULL),  m_connectionInfo(connInfo),  m_connectionFlags(CONNECTION_ASYNC)  { -    m_worker = new DatabaseWorker(m_queue, this); +    m_worker = Trinity::make_unique<DatabaseWorker>(m_queue, this);  }  MySQLConnection::~MySQLConnection()  { -    delete m_worker; - -    for (size_t i = 0; i < m_stmts.size(); ++i) -        delete m_stmts[i]; - -    if (m_Mysql) -        mysql_close(m_Mysql); +    Close();  }  void MySQLConnection::Close()  { -    /// Only close us if we're not operating -    delete this; +    // Stop the worker thread before the statements are cleared +    m_worker.reset(); + +    m_stmts.clear(); + +    if (m_Mysql) +    { +        mysql_close(m_Mysql); +        m_Mysql = nullptr; +    }  }  uint32 MySQLConnection::Open() @@ -412,7 +413,7 @@ int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)  MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index)  {      ASSERT(index < m_stmts.size()); -    MySQLPreparedStatement* ret = m_stmts[index]; +    MySQLPreparedStatement* ret = m_stmts[index].get();      if (!ret)          TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement %u on database `%s`, connection type: %s.",              index, m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); @@ -424,16 +425,12 @@ void MySQLConnection::PrepareStatement(uint32 index, const char* sql, Connection  {      m_queries.insert(PreparedStatementMap::value_type(index, std::make_pair(sql, flags))); -    // For reconnection case -    if (m_reconnecting) -        delete m_stmts[index]; -      // Check if specified query should be prepared on this connection      // i.e. don't prepare async statements on synchronous connections      // to save memory that will not be used.      if (!(m_connectionFlags & flags))      { -        m_stmts[index] = NULL; +        m_stmts[index].reset();          return;      } @@ -455,8 +452,7 @@ void MySQLConnection::PrepareStatement(uint32 index, const char* sql, Connection          }          else          { -            MySQLPreparedStatement* mStmt = new MySQLPreparedStatement(stmt); -            m_stmts[index] = mStmt; +            m_stmts[index] = Trinity::make_unique<MySQLPreparedStatement>(stmt);          }      }  } @@ -477,7 +473,7 @@ PreparedResultSet* MySQLConnection::Query(PreparedStatement* stmt)      return new PreparedResultSet(stmt->m_stmt->GetSTMT(), result, rowCount, fieldCount);  } -bool MySQLConnection::_HandleMySQLErrno(uint32 errNo) +bool MySQLConnection::_HandleMySQLErrno(uint32 errNo, uint8 attempts /*= 5*/)  {      switch (errNo)      { @@ -486,9 +482,21 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo)          case CR_INVALID_CONN_HANDLE:          case CR_SERVER_LOST_EXTENDED:          { +            if (m_Mysql) +            { +                TC_LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!"); + +                mysql_close(GetHandle()); +                m_Mysql = nullptr; +            } + +            /*no break*/ +        } +        case CR_CONN_HOST_ERROR: +        { +            TC_LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server..."); +              m_reconnecting = true; -            uint64 oldThreadId = mysql_thread_id(GetHandle()); -            mysql_close(GetHandle());              uint32 const lErrno = Open();              if (!lErrno) @@ -496,24 +504,37 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo)                  // Don't remove 'this' pointer unless you want to skip loading all prepared statements...                  if (!this->PrepareStatements())                  { -                    TC_LOG_ERROR("sql.sql", "Could not re-prepare statements!"); -                    Close(); -                    return false; +                    TC_LOG_FATAL("sql.sql", "Could not re-prepare statements!"); +                    std::this_thread::sleep_for(std::chrono::seconds(10)); +                    std::abort();                  } -                TC_LOG_INFO("sql.sql", "Connection to the MySQL server is active."); -                if (oldThreadId != mysql_thread_id(GetHandle())) -                    TC_LOG_INFO("sql.sql", "Successfully reconnected to %s @%s:%s (%s).", -                        m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(), -                            (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous"); +                TC_LOG_INFO("sql.sql", "Successfully reconnected to %s @%s:%s (%s).", +                    m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(), +                        (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");                  m_reconnecting = false;                  return true;              } -            // It's possible this attempted reconnect throws 2006 at us. To prevent crazy recursive calls, sleep here. -            std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds -            return _HandleMySQLErrno(lErrno);                     // Call self (recursive) +            if ((--attempts) == 0) +            { +                // Shut down the server when the mysql server isn't +                // reachable for some time +                TC_LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, " +                             "terminating the server to prevent data corruption!"); + +                // We could also initiate a shutdown through using std::raise(SIGTERM) +                std::this_thread::sleep_for(std::chrono::seconds(10)); +                std::abort(); +            } +            else +            { +                // It's possible this attempted reconnect throws 2006 at us. +                // To prevent crazy recursive calls, sleep here. +                std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds +                return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive) +            }          }          case ER_LOCK_DEADLOCK: diff --git a/src/server/database/Database/MySQLConnection.h b/src/server/database/Database/MySQLConnection.h index a981caa607e..566995988c0 100644 --- a/src/server/database/Database/MySQLConnection.h +++ b/src/server/database/Database/MySQLConnection.h @@ -35,7 +35,7 @@ enum ConnectionFlags      CONNECTION_BOTH = CONNECTION_ASYNC | CONNECTION_SYNCH  }; -struct MySQLConnectionInfo +struct TC_DATABASE_API MySQLConnectionInfo  {      explicit MySQLConnectionInfo(std::string const& infoString)      { @@ -62,7 +62,7 @@ struct MySQLConnectionInfo  typedef std::map<uint32 /*index*/, std::pair<std::string /*query*/, ConnectionFlags /*sync/async*/> > PreparedStatementMap; -class MySQLConnection +class TC_DATABASE_API MySQLConnection  {      template <class T> friend class DatabaseWorkerPool;      friend class PingOperation; @@ -116,18 +116,18 @@ class MySQLConnection          virtual void DoPrepareStatements() = 0;      protected: -        std::vector<MySQLPreparedStatement*> m_stmts;         //! PreparedStatements storage +        std::vector<std::unique_ptr<MySQLPreparedStatement>> m_stmts; //! PreparedStatements storage          PreparedStatementMap                 m_queries;       //! Query storage          bool                                 m_reconnecting;  //! Are we reconnecting?          bool                                 m_prepareError;  //! Was there any error while preparing statements?      private: -        bool _HandleMySQLErrno(uint32 errNo); +        bool _HandleMySQLErrno(uint32 errNo, uint8 attempts = 5);      private:          ProducerConsumerQueue<SQLOperation*>* m_queue;      //! Queue shared with other asynchronous connections. -        DatabaseWorker*       m_worker;                     //! Core worker task. -        MYSQL *               m_Mysql;                      //! MySQL Handle. +        std::unique_ptr<DatabaseWorker> m_worker;           //! Core worker task. +        MYSQL*                m_Mysql;                      //! MySQL Handle.          MySQLConnectionInfo&  m_connectionInfo;             //! Connection info (used for logging)          ConnectionFlags       m_connectionFlags;            //! Connection flags (for preparing relevant statements)          std::mutex            m_Mutex; diff --git a/src/server/database/Database/MySQLThreading.h b/src/server/database/Database/MySQLThreading.h index 1cfa11d7e5b..b6083500989 100644 --- a/src/server/database/Database/MySQLThreading.h +++ b/src/server/database/Database/MySQLThreading.h @@ -20,7 +20,7 @@  #include "Log.h" -class MySQL +class TC_DATABASE_API MySQL  {      public:          static void Library_Init() diff --git a/src/server/database/Database/PreparedStatement.cpp b/src/server/database/Database/PreparedStatement.cpp index 848a923c75d..119f1d4c93b 100644 --- a/src/server/database/Database/PreparedStatement.cpp +++ b/src/server/database/Database/PreparedStatement.cpp @@ -344,7 +344,7 @@ void MySQLPreparedStatement::setString(const uint8 index, const char* value)      CheckValidIndex(index);      m_paramsSet[index] = true;      MYSQL_BIND* param = &m_bind[index]; -    size_t len = strlen(value) + 1; +    uint32 len = uint32(strlen(value) + 1);      param->buffer_type = MYSQL_TYPE_VAR_STRING;      delete [] static_cast<char *>(param->buffer);      param->buffer = new char[len]; diff --git a/src/server/database/Database/PreparedStatement.h b/src/server/database/Database/PreparedStatement.h index 7d6c98463d0..faaec27014f 100644 --- a/src/server/database/Database/PreparedStatement.h +++ b/src/server/database/Database/PreparedStatement.h @@ -70,7 +70,7 @@ struct PreparedStatementData  class MySQLPreparedStatement;  //- Upper-level class that is used in code -class PreparedStatement +class TC_DATABASE_API PreparedStatement  {      friend class PreparedStatementTask;      friend class MySQLPreparedStatement; @@ -109,7 +109,7 @@ class PreparedStatement  //- Class of which the instances are unique per MySQLConnection  //- access to these class objects is only done when a prepared statement task  //- is executed. -class MySQLPreparedStatement +class TC_DATABASE_API MySQLPreparedStatement  {      friend class MySQLConnection;      friend class PreparedStatement; @@ -157,7 +157,7 @@ typedef std::future<PreparedQueryResult> PreparedQueryResultFuture;  typedef std::promise<PreparedQueryResult> PreparedQueryResultPromise;  //- Lower-level class, enqueuable operation -class PreparedStatementTask : public SQLOperation +class TC_DATABASE_API PreparedStatementTask : public SQLOperation  {      public:          PreparedStatementTask(PreparedStatement* stmt, bool async = false); diff --git a/src/server/database/Database/QueryCallback.h b/src/server/database/Database/QueryCallback.h new file mode 100644 index 00000000000..5f6ae74da4f --- /dev/null +++ b/src/server/database/Database/QueryCallback.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * 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 _QUERY_CALLBACK_H +#define _QUERY_CALLBACK_H + +#include <future> +#include "QueryResult.h" + +typedef std::future<QueryResult> QueryResultFuture; +typedef std::promise<QueryResult> QueryResultPromise; +typedef std::future<PreparedQueryResult> PreparedQueryResultFuture; +typedef std::promise<PreparedQueryResult> PreparedQueryResultPromise; + +#define CALLBACK_STAGE_INVALID uint8(-1) + +template <typename Result, typename ParamType, bool chain = false> +class QueryCallback +{ +    public: +        QueryCallback() : _param(), _stage(chain ? 0 : CALLBACK_STAGE_INVALID)  { } + +        //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery +        void SetFutureResult(std::future<Result> value) +        { +            _result = std::move(value); +        } + +        std::future<Result>& GetFutureResult() +        { +            return _result; +        } + +        int IsReady() +        { +            return _result.valid() && _result.wait_for(std::chrono::seconds(0)) == std::future_status::ready; +        } + +        void GetResult(Result& res) +        { +            res = _result.get(); +        } + +        void FreeResult() +        { +            // Nothing to do here, the constructor of std::future will take care of the cleanup +        } + +        void SetParam(ParamType value) +        { +            _param = value; +        } + +        ParamType GetParam() +        { +            return _param; +        } + +        //! Resets the stage of the callback chain +        void ResetStage() +        { +            if (!chain) +                return; + +            _stage = 0; +        } + +        //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly +        void NextStage() +        { +            if (!chain) +                return; + +            ++_stage; +        } + +        //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) +        uint8 GetStage() +        { +            return _stage; +        } + +        //! Resets all underlying variables (param, result and stage) +        void Reset() +        { +            SetParam(ParamType()); +            FreeResult(); +            ResetStage(); +        } + +    private: +        std::future<Result> _result; +        ParamType _param; +        uint8 _stage; + +        QueryCallback(QueryCallback const& right) = delete; +        QueryCallback& operator=(QueryCallback const& right) = delete; +}; + +template <typename Result, typename ParamType1, typename ParamType2, bool chain = false> +class QueryCallback_2 +{ +    public: +        QueryCallback_2() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) { } + +        //! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery +        void SetFutureResult(std::future<Result> value) +        { +            _result = std::move(value); +        } + +        std::future<Result>& GetFutureResult() +        { +            return _result; +        } + +        int IsReady() +        { +            return _result.valid() && _result.wait_for(std::chrono::seconds(0)) == std::future_status::ready; +        } + +        void GetResult(Result& res) +        { +            res = _result.get(); +        } + +        void FreeResult() +        { +            // Nothing to do here, the constructor of std::future will take care of the cleanup +        } + +        void SetFirstParam(ParamType1 value) +        { +            _param_1 = value; +        } + +        void SetSecondParam(ParamType2 value) +        { +            _param_2 = value; +        } + +        ParamType1 GetFirstParam() +        { +            return _param_1; +        } + +        ParamType2 GetSecondParam() +        { +            return _param_2; +        } + +        //! Resets the stage of the callback chain +        void ResetStage() +        { +            if (!chain) +                return; + +            _stage = 0; +        } + +        //! Advances the callback chain to the next stage, so upper level code can act on its results accordingly +        void NextStage() +        { +            if (!chain) +                return; + +            ++_stage; +        } + +        //! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid) +        uint8 GetStage() +        { +            return _stage; +        } + +        //! Resets all underlying variables (param, result and stage) +        void Reset() +        { +            SetFirstParam(NULL); +            SetSecondParam(NULL); +            FreeResult(); +            ResetStage(); +        } + +    private: +        std::future<Result> _result; +        ParamType1 _param_1; +        ParamType2 _param_2; +        uint8 _stage; + +        QueryCallback_2(QueryCallback_2 const& right) = delete; +        QueryCallback_2& operator=(QueryCallback_2 const& right) = delete; +}; + +#endif // _QUERY_CALLBACK_H diff --git a/src/server/database/Database/QueryHolder.h b/src/server/database/Database/QueryHolder.h index 9a5a03fda42..2446a4db2bd 100644 --- a/src/server/database/Database/QueryHolder.h +++ b/src/server/database/Database/QueryHolder.h @@ -20,7 +20,7 @@  #include <future> -class SQLQueryHolder +class TC_DATABASE_API SQLQueryHolder  {      friend class SQLQueryHolderTask;      private: @@ -46,7 +46,7 @@ class SQLQueryHolder  typedef std::future<SQLQueryHolder*> QueryResultHolderFuture;  typedef std::promise<SQLQueryHolder*> QueryResultHolderPromise; -class SQLQueryHolderTask : public SQLOperation +class TC_DATABASE_API SQLQueryHolderTask : public SQLOperation  {      private:          SQLQueryHolder* m_holder; diff --git a/src/server/database/Database/QueryResult.cpp b/src/server/database/Database/QueryResult.cpp index f02457f67ca..db9e737830c 100644 --- a/src/server/database/Database/QueryResult.cpp +++ b/src/server/database/Database/QueryResult.cpp @@ -76,7 +76,7 @@ m_length(NULL)      std::size_t rowSize = 0;      for (uint32 i = 0; i < m_fieldCount; ++i)      { -        size_t size = Field::SizeForType(&field[i]); +        uint32 size = Field::SizeForType(&field[i]);          rowSize += size;          m_rBind[i].buffer_type = field[i].type; diff --git a/src/server/database/Database/QueryResult.h b/src/server/database/Database/QueryResult.h index d4d63b5ec85..3b1691db1a6 100644 --- a/src/server/database/Database/QueryResult.h +++ b/src/server/database/Database/QueryResult.h @@ -27,7 +27,7 @@  #endif  #include <mysql.h> -class ResultSet +class TC_DATABASE_API ResultSet  {      public:          ResultSet(MYSQL_RES* result, MYSQL_FIELD* fields, uint64 rowCount, uint32 fieldCount); @@ -60,7 +60,7 @@ class ResultSet  typedef std::shared_ptr<ResultSet> QueryResult; -class PreparedResultSet +class TC_DATABASE_API PreparedResultSet  {      public:          PreparedResultSet(MYSQL_STMT* stmt, MYSQL_RES* result, uint64 rowCount, uint32 fieldCount); diff --git a/src/server/database/Database/SQLOperation.h b/src/server/database/Database/SQLOperation.h index f0500d1f232..5b3032eab87 100644 --- a/src/server/database/Database/SQLOperation.h +++ b/src/server/database/Database/SQLOperation.h @@ -53,7 +53,7 @@ union SQLResultSetUnion  class MySQLConnection; -class SQLOperation +class TC_DATABASE_API SQLOperation  {      public:          SQLOperation(): m_conn(NULL) { } diff --git a/src/server/database/Database/Transaction.h b/src/server/database/Database/Transaction.h index 5780c0363d9..7b5b6addfe4 100644 --- a/src/server/database/Database/Transaction.h +++ b/src/server/database/Database/Transaction.h @@ -25,7 +25,7 @@  class PreparedStatement;  /*! Transactions, high level class. */ -class Transaction +class TC_DATABASE_API Transaction  {      friend class TransactionTask;      friend class MySQLConnection; @@ -58,7 +58,7 @@ class Transaction  typedef std::shared_ptr<Transaction> SQLTransaction;  /*! Low level class*/ -class TransactionTask : public SQLOperation +class TC_DATABASE_API TransactionTask : public SQLOperation  {      template <class T> friend class DatabaseWorkerPool;      friend class DatabaseWorker; diff --git a/src/server/database/Logging/AppenderDB.h b/src/server/database/Logging/AppenderDB.h index a6acc66b48c..225ae969802 100644 --- a/src/server/database/Logging/AppenderDB.h +++ b/src/server/database/Logging/AppenderDB.h @@ -20,7 +20,7 @@  #include "Appender.h" -class AppenderDB: public Appender +class TC_DATABASE_API AppenderDB: public Appender  {      public:          typedef std::integral_constant<AppenderType, APPENDER_DB>::type TypeIndex; diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index 170954a86f4..8515da9f6f8 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -21,57 +21,41 @@  #include "UpdateFetcher.h"  #include "DatabaseLoader.h"  #include "Config.h" +#include "BuiltInConfig.h" +#include "StartProcess.h"  #include <fstream>  #include <iostream>  #include <unordered_map> -#include <boost/process.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/iostreams/copy.hpp> -#include <boost/iostreams/device/file_descriptor.hpp> -#include <boost/system/system_error.hpp> -using namespace boost::process; -using namespace boost::process::initializers; -using namespace boost::iostreams; - -std::string DBUpdaterUtil::GetMySqlCli() +std::string DBUpdaterUtil::GetCorrectedMySQLExecutable()  {      if (!corrected_path().empty())          return corrected_path();      else -    { -        std::string const entry = sConfigMgr->GetStringDefault("Updates.MySqlCLIPath", ""); -        if (!entry.empty()) -            return entry; -        else -            return GitRevision::GetMySQLExecutable(); -    } +        return BuiltInConfig::GetMySQLExecutable();  }  bool DBUpdaterUtil::CheckExecutable()  { -    boost::filesystem::path exe(GetMySqlCli()); +    boost::filesystem::path exe(GetCorrectedMySQLExecutable());      if (!exists(exe))      {          exe.clear(); -        try -        { -            exe = search_path("mysql"); -        } -        catch (std::runtime_error&) +        if (auto path = Trinity::SearchExecutableInPath("mysql"))          { -        } +            exe = std::move(*path); -        if (!exe.empty() && exists(exe)) -        { -            // Correct the path to the cli -            corrected_path() = absolute(exe).generic_string(); -            return true; +            if (!exe.empty() && exists(exe)) +            { +                // Correct the path to the cli +                corrected_path() = absolute(exe).generic_string(); +                return true; +            }          } -        TC_LOG_FATAL("sql.updates", "Didn't find executeable mysql binary at \'%s\' or in path, correct the path in the *.conf (\"Updates.MySqlCLIPath\").", +        TC_LOG_FATAL("sql.updates", "Didn't find any executable MySQL binary at \'%s\' or in path, correct the path in the *.conf (\"MySQLExecutable\").",              absolute(exe).generic_string().c_str());          return false; @@ -85,16 +69,6 @@ std::string& DBUpdaterUtil::corrected_path()      return path;  } -template<class T> -std::string DBUpdater<T>::GetSourceDirectory() -{ -    std::string const entry = sConfigMgr->GetStringDefault("Updates.SourcePath", ""); -    if (!entry.empty()) -        return entry; -    else -        return GitRevision::GetSourceDirectory(); -} -  // Auth Database  template<>  std::string DBUpdater<LoginDatabaseConnection>::GetConfigEntry() @@ -111,7 +85,8 @@ std::string DBUpdater<LoginDatabaseConnection>::GetTableName()  template<>  std::string DBUpdater<LoginDatabaseConnection>::GetBaseFile()  { -    return DBUpdater<LoginDatabaseConnection>::GetSourceDirectory() + "/sql/base/auth_database.sql"; +    return BuiltInConfig::GetSourceDirectory() + +        "/sql/base/auth_database.sql";  }  template<> @@ -169,7 +144,8 @@ std::string DBUpdater<CharacterDatabaseConnection>::GetTableName()  template<>  std::string DBUpdater<CharacterDatabaseConnection>::GetBaseFile()  { -    return DBUpdater<CharacterDatabaseConnection>::GetSourceDirectory() + "/sql/base/characters_database.sql"; +    return BuiltInConfig::GetSourceDirectory() + +        "/sql/base/characters_database.sql";  }  template<> @@ -202,7 +178,7 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)      // Path of temp file      static Path const temp("create_table.sql"); -    // Create temporary query to use external mysql cli +    // Create temporary query to use external MySQL CLi      std::ofstream file(temp.generic_string());      if (!file.is_open())      { @@ -221,7 +197,7 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool)      }      catch (UpdateException&)      { -        TC_LOG_FATAL("sql.updates", "Failed to create database %s! Has the user `CREATE` priviliges?", pool.GetConnectionInfo()->database.c_str()); +        TC_LOG_FATAL("sql.updates", "Failed to create database %s! Does the user (named in *.conf) have `CREATE` privileges on the MySQL server?", pool.GetConnectionInfo()->database.c_str());          boost::filesystem::remove(temp);          return false;      } @@ -239,7 +215,7 @@ bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool)      TC_LOG_INFO("sql.updates", "Updating %s database...", DBUpdater<T>::GetTableName().c_str()); -    Path const sourceDirectory(GetSourceDirectory()); +    Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());      if (!is_directory(sourceDirectory))      { @@ -304,7 +280,7 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)          {              case LOCATION_REPOSITORY:              { -                TC_LOG_ERROR("sql.updates", ">> Base file \"%s\" is missing, try to clone the source again.", +                TC_LOG_ERROR("sql.updates", ">> Base file \"%s\" is missing. Try fixing it by cloning the source again.",                      base.generic_string().c_str());                  break; @@ -312,7 +288,7 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)              case LOCATION_DOWNLOAD:              {                  TC_LOG_ERROR("sql.updates", ">> File \"%s\" is missing, download it from \"https://github.com/TrinityCore/TrinityCore/releases\"" \ -                    " and place it in your server directory.", base.filename().generic_string().c_str()); +                    " uncompress it and place the file TDB_full_world_(a_variable_name).sql in your worldserver directory.", base.filename().generic_string().c_str());                  break;              }          } @@ -379,7 +355,7 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos      if (!std::isdigit(port_or_socket[0]))      { -        // We can't check here if host == "." because is named localhost if socket option is enabled +        // We can't check if host == "." here, because it is named localhost if socket option is enabled          args.push_back("-P0");          args.push_back("--protocol=SOCKET");          args.push_back("-S" + port_or_socket); @@ -400,55 +376,23 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos      if (!database.empty())          args.push_back(database); -    // ToDo: use the existing query in memory as virtual file if possible -    file_descriptor_source source(path); - -    uint32 ret; -    try -    { -        boost::process::pipe outPipe = create_pipe(); -        boost::process::pipe errPipe = create_pipe(); - -        child c = execute(run_exe( -                    boost::filesystem::absolute(DBUpdaterUtil::GetMySqlCli()).generic_string()), -                    set_args(args), bind_stdin(source), throw_on_error(), -                    bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), -                    bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); - -        file_descriptor_source mysqlOutfd(outPipe.source, close_handle); -        file_descriptor_source mysqlErrfd(errPipe.source, close_handle); - -        stream<file_descriptor_source> mysqlOutStream(mysqlOutfd); -        stream<file_descriptor_source> mysqlErrStream(mysqlErrfd); - -        std::stringstream out; -        std::stringstream err; - -        copy(mysqlOutStream, out); -        copy(mysqlErrStream, err); - -        TC_LOG_INFO("sql.updates", "%s", out.str().c_str()); -        TC_LOG_ERROR("sql.updates", "%s", err.str().c_str()); - -        ret = wait_for_exit(c); -    } -    catch (boost::system::system_error&) -    { -        ret = EXIT_FAILURE; -    } - -    source.close(); +    // Invokes a mysql process which doesn't leak credentials to logs +    int const ret = Trinity::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), args, +                                 "sql.updates", path.generic_string(), true);      if (ret != EXIT_SUCCESS)      {          TC_LOG_FATAL("sql.updates", "Applying of file \'%s\' to database \'%s\' failed!" \ -            " If you are an user pull the latest revision from the repository. If you are a developer fix your sql query.", +            " If you are a user, please pull the latest revision from the repository. " +            "Also make sure you have not applied any of the databases with your sql client. " +            "You cannot use auto-update system and import sql files from TrinityCore repository with your sql client. " +            "If you are a developer, please fix your sql query.",              path.generic_string().c_str(), pool.GetConnectionInfo()->database.c_str());          throw UpdateException("update failed");      }  } -template class DBUpdater<LoginDatabaseConnection>; -template class DBUpdater<WorldDatabaseConnection>; -template class DBUpdater<CharacterDatabaseConnection>; +template class TC_DATABASE_API DBUpdater<LoginDatabaseConnection>; +template class TC_DATABASE_API DBUpdater<WorldDatabaseConnection>; +template class TC_DATABASE_API DBUpdater<CharacterDatabaseConnection>; diff --git a/src/server/database/Updater/DBUpdater.h b/src/server/database/Updater/DBUpdater.h index c9792ffe060..cc5d3aad68b 100644 --- a/src/server/database/Updater/DBUpdater.h +++ b/src/server/database/Updater/DBUpdater.h @@ -23,7 +23,7 @@  #include <string>  #include <boost/filesystem.hpp> -class UpdateException : public std::exception +class TC_DATABASE_API UpdateException : public std::exception  {  public:      UpdateException(std::string const& msg) : _msg(msg) { } @@ -41,7 +41,7 @@ enum BaseLocation      LOCATION_DOWNLOAD  }; -struct UpdateResult +struct TC_DATABASE_API UpdateResult  {      UpdateResult()          : updated(0), recent(0), archived(0) { } @@ -57,7 +57,7 @@ struct UpdateResult  class DBUpdaterUtil  {  public: -    static std::string GetMySqlCli(); +    static std::string GetCorrectedMySQLExecutable();      static bool CheckExecutable(); @@ -66,13 +66,11 @@ private:  };  template <class T> -class DBUpdater +class TC_DATABASE_API DBUpdater  {  public:      using Path = boost::filesystem::path; -    static std::string GetSourceDirectory(); -      static inline std::string GetConfigEntry();      static inline std::string GetTableName(); diff --git a/src/server/database/Updater/UpdateFetcher.cpp b/src/server/database/Updater/UpdateFetcher.cpp index fd0dbdd4b5a..7dc0a307ca2 100644 --- a/src/server/database/Updater/UpdateFetcher.cpp +++ b/src/server/database/Updater/UpdateFetcher.cpp @@ -18,6 +18,7 @@  #include "UpdateFetcher.h"  #include "Log.h"  #include "Util.h" +#include "SHA1.h"  #include <fstream>  #include <chrono> @@ -25,7 +26,6 @@  #include <sstream>  #include <exception>  #include <unordered_map> -#include <openssl/sha.h>  using namespace boost::filesystem; @@ -137,24 +137,33 @@ UpdateFetcher::AppliedFileStorage UpdateFetcher::ReceiveAppliedFiles() const      return map;  } -UpdateFetcher::SQLUpdate UpdateFetcher::ReadSQLUpdate(boost::filesystem::path const& file) const +std::string UpdateFetcher::ReadSQLUpdate(boost::filesystem::path const& file) const  {      std::ifstream in(file.c_str()); -    WPFatal(in.is_open(), "Could not read an update file."); +    if (!in.is_open()) +    { +        TC_LOG_FATAL("sql.updates", "Failed to open the sql update \"%s\" for reading! " +                     "Stopping the server to keep the database integrity, " +                     "try to identify and solve the issue or disabled the database updater.", +                     file.generic_string().c_str()); -    auto const start_pos = in.tellg(); -    in.ignore(std::numeric_limits<std::streamsize>::max()); -    auto const char_count = in.gcount(); -    in.seekg(start_pos); +        throw UpdateException("Opening the sql update failed!"); +    } -    SQLUpdate const update(new std::string(char_count, char{})); +    auto update = [&in]  { +        std::ostringstream ss; +        ss << in.rdbuf(); +        return ss.str(); +    }(); -    in.read(&(*update)[0], update->size());      in.close();      return update;  } -UpdateResult UpdateFetcher::Update(bool const redundancyChecks, bool const allowRehash, bool const archivedRedundancy, int32 const cleanDeadReferencesMaxCount) const +UpdateResult UpdateFetcher::Update(bool const redundancyChecks, +                                   bool const allowRehash, +                                   bool const archivedRedundancy, +                                   int32 const cleanDeadReferencesMaxCount) const  {      LocaleFileStorage const available = GetFileList();      AppliedFileStorage applied = ReceiveAppliedFiles(); @@ -200,11 +209,8 @@ UpdateResult UpdateFetcher::Update(bool const redundancyChecks, bool const allow              }          } -        // Read update from file -        SQLUpdate const update = ReadSQLUpdate(availableQuery.first); - -        // Calculate hash -        std::string const hash = CalculateHash(update); +        // Calculate a Sha1 hash based on query content. +        std::string const hash = CalculateSHA1Hash(ReadSQLUpdate(availableQuery.first));          UpdateMode mode = MODE_APPLY; @@ -327,15 +333,6 @@ UpdateResult UpdateFetcher::Update(bool const redundancyChecks, bool const allow      return UpdateResult(importedUpdates, countRecentUpdates, countArchivedUpdates);  } -std::string UpdateFetcher::CalculateHash(SQLUpdate const& query) const -{ -    // Calculate a Sha1 hash based on query content. -    unsigned char digest[SHA_DIGEST_LENGTH]; -    SHA1((unsigned char*)query->c_str(), query->length(), (unsigned char*)&digest); - -    return ByteArrayToHexStr(digest, SHA_DIGEST_LENGTH); -} -  uint32 UpdateFetcher::Apply(Path const& path) const  {      using Time = std::chrono::high_resolution_clock; @@ -347,7 +344,7 @@ uint32 UpdateFetcher::Apply(Path const& path) const      _applyFile(path);      // Return time the query took to apply -    return std::chrono::duration_cast<std::chrono::milliseconds>(Time::now() - begin).count(); +    return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(Time::now() - begin).count());  }  void UpdateFetcher::UpdateEntry(AppliedFileEntry const& entry, uint32 const speed) const diff --git a/src/server/database/Updater/UpdateFetcher.h b/src/server/database/Updater/UpdateFetcher.h index 22a0d08c7f8..cabc3c2fce3 100644 --- a/src/server/database/Updater/UpdateFetcher.h +++ b/src/server/database/Updater/UpdateFetcher.h @@ -25,7 +25,7 @@  #include <memory>  #include <vector> -class UpdateFetcher +class TC_DATABASE_API UpdateFetcher  {      typedef boost::filesystem::path Path; @@ -103,16 +103,15 @@ private:      typedef std::unordered_map<std::string, std::string> HashToFileNameStorage;      typedef std::unordered_map<std::string, AppliedFileEntry> AppliedFileStorage;      typedef std::vector<UpdateFetcher::DirectoryEntry> DirectoryStorage; -    typedef std::shared_ptr<std::string> SQLUpdate;      LocaleFileStorage GetFileList() const; -    void FillFileListRecursively(Path const& path, LocaleFileStorage& storage, State const state, uint32 const depth) const; +    void FillFileListRecursively(Path const& path, LocaleFileStorage& storage, +        State const state, uint32 const depth) const;      DirectoryStorage ReceiveIncludedDirectories() const;      AppliedFileStorage ReceiveAppliedFiles() const; -    SQLUpdate ReadSQLUpdate(Path const& file) const; -    std::string CalculateHash(SQLUpdate const& query) const; +    std::string ReadSQLUpdate(Path const& file) const;      uint32 Apply(Path const& path) const;  | 
