From 2f8c374569b08945badc0561e24725c24ea3e90c Mon Sep 17 00:00:00 2001 From: Dawnforger Date: Fri, 8 May 2026 20:11:43 -0500 Subject: [PATCH] fix(db): MariaDB 10.6+ server and connector compatibility - Parse real MariaDB version after MySQL 5.5.5 compatibility prefix for DatabaseIncompatibleVersion checks. - Relax client checks when building against MariaDB C connector (MARIADB_VERSION_ID); keep Oracle MySQL 8.0+ rules otherwise. - Use mysql_stmt_bind_param on MariaDB; mysql_stmt_bind_named_param only for MySQL 8.3+ without MariaDB headers. - SSL: MYSQL_OPT_SSL_ENFORCE on MariaDB connector, MYSQL_OPT_SSL_MODE elsewhere. - Track custom_script_loader.cpp so static script link succeeds; CMake find_package(MySQL) requires COMPONENTS lib on CMake 3.16. --- .gitignore | 1 + CMakeLists.txt | 2 +- .../database/Database/DatabaseWorkerPool.cpp | 81 ++++++++++++++----- .../database/Database/DatabaseWorkerPool.h | 12 +++ .../database/Database/MySQLConnection.cpp | 12 ++- .../scripts/Custom/custom_script_loader.cpp | 26 ++++++ 6 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 src/server/scripts/Custom/custom_script_loader.cpp diff --git a/.gitignore b/.gitignore index 08cc229..4c851e6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /data/sql/custom/* /src/server/scripts/Custom/* !/src/server/scripts/Custom/README.md +!/src/server/scripts/Custom/custom_script_loader.cpp /*.override.yml /*.override.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index 494109c..7e850ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ include(ConfigInstall) CU_RUN_HOOK("AFTER_LOAD_CMAKE_MODULES") find_package(PCHSupport) -find_package(MySQL REQUIRED) +find_package(MySQL REQUIRED COMPONENTS lib) if(UNIX AND WITH_PERFTOOLS) find_package(Gperftools) diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp index ee19a5a..f4ad764 100644 --- a/src/server/database/Database/DatabaseWorkerPool.cpp +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -31,6 +31,7 @@ #include "SQLOperation.h" #include "Transaction.h" #include "WorldDatabase.h" +#include #include #include #include @@ -59,10 +60,18 @@ DatabaseWorkerPool::DatabaseWorkerPool() : { WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe."); - bool isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION; - bool isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID; + bool isSupportClientDB; + bool isSameClientDB; +#ifdef MARIADB_VERSION_ID + isSupportClientDB = (MARIADB_VERSION_ID >= MIN_MARIADB_CLIENT_VERSION_ID); + // MariaDB packages often differ between build host and runtime; strict id match is too brittle. + isSameClientDB = true; +#else + isSupportClientDB = mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION; + isSameClientDB = mysql_get_client_version() == MYSQL_VERSION_ID; +#endif - WPFatal(isSupportClientDB, "AzerothCore does not support MySQL versions below 8.0\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).", + WPFatal(isSupportClientDB, "AzerothCore does not support this database client library.\n\nFound version: {} / {}. Server compiled with: {}.\nSearch the wiki for ACE00043 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00043).", mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID); WPFatal(isSameClientDB, "Used MySQL library version ({} id {}) does not match the version id used to compile AzerothCore (id {}).\nSearch the wiki for ACE00046 in Common Errors (https://www.azerothcore.org/wiki/common-errors#ace00046).", mysql_get_client_info(), mysql_get_client_version(), MYSQL_VERSION_ID); @@ -386,32 +395,60 @@ void DatabaseWorkerPool::KeepAlive() */ bool DatabaseIncompatibleVersion(std::string const mysqlVersion) { - // anon func to turn a version string into an array of uint8 - // "1.2.3" => [1, 2, 3] - auto parse = [](std::string const& input) + auto parseTriplet = [](std::string const& input) -> std::array { - std::vector result; - std::istringstream parser(input); - result.push_back(parser.get()); - for (int i = 1; i < 3; i++) + std::array v = { 0, 0, 0 }; + size_t idx = 0; + std::string num; + for (char ch : input) { - // Skip period - parser.get(); - // Append int from parser to output - result.push_back(parser.get()); + if (ch >= '0' && ch <= '9') + num.push_back(ch); + else if (ch == '.') + { + if (!num.empty() && idx < 3) + { + v[idx++] = static_cast(std::stoul(num)); + num.clear(); + } + } + else + break; } - return result; + if (!num.empty() && idx < 3) + v[idx] = static_cast(std::stoul(num)); + return v; }; - // default to values for MySQL - uint8 offset = 0; + auto compareVersion = [](std::array const& a, std::array const& b) -> int + { + if (a[0] != b[0]) + return (a[0] < b[0]) ? -1 : 1; + if (a[1] != b[1]) + return (a[1] < b[1]) ? -1 : 1; + if (a[2] != b[2]) + return (a[2] < b[2]) ? -1 : 1; + return 0; + }; + + std::string ver = mysqlVersion; std::string minVersion = MIN_MYSQL_SERVER_VERSION; - auto parsedMySQLVersion = parse(mysqlVersion.substr(offset)); - auto parsedMinVersion = parse(minVersion); + // MariaDB: mysql_get_server_info() often starts with MySQL-compat "5.5.5-" then real version, e.g. + // "5.5.5-10.6.11-MariaDB-1:10.6.11+maria~ubu2004" + if (ver.find("MariaDB") != std::string::npos) + { + size_t const firstDash = ver.find('-'); + if (firstDash != std::string::npos) + { + size_t const secondDash = ver.find('-', firstDash + 1); + if (secondDash != std::string::npos) + ver = ver.substr(firstDash + 1, secondDash - firstDash - 1); + } + minVersion = MIN_MARIADB_SERVER_VERSION; + } - return std::lexicographical_compare(parsedMySQLVersion.begin(), parsedMySQLVersion.end(), - parsedMinVersion.begin(), parsedMinVersion.end()); + return compareVersion(parseTriplet(ver), parseTriplet(minVersion)) < 0; } template @@ -442,7 +479,7 @@ uint32 DatabaseWorkerPool::OpenConnections(InternalIndex type, uint8 numConne } else if (DatabaseIncompatibleVersion(connection->GetServerInfo())) { - LOG_ERROR("sql.driver", "AzerothCore does not support MySQL versions below 8.0\n\nFound server version: {}. Server compiled with: {}.", + LOG_ERROR("sql.driver", "Database server version is too old.\n\nFound server version: {}. Client library compile id: {}.", connection->GetServerInfo(), MYSQL_VERSION_ID); return 1; } diff --git a/src/server/database/Database/DatabaseWorkerPool.h b/src/server/database/Database/DatabaseWorkerPool.h index d9b418b..8547056 100644 --- a/src/server/database/Database/DatabaseWorkerPool.h +++ b/src/server/database/Database/DatabaseWorkerPool.h @@ -32,12 +32,24 @@ */ #define MIN_MYSQL_CLIENT_VERSION 80000u +/** +* @def MIN_MARIADB_CLIENT_VERSION_ID +* Minimum MariaDB connector MARIADB_VERSION_ID (10.4.0 => 100400) +*/ +#define MIN_MARIADB_CLIENT_VERSION_ID 100400u + /** * @def MIN_MYSQL_SERVER_VERSION * The minimum MySQL Server Version */ #define MIN_MYSQL_SERVER_VERSION "8.0.0" +/** +* @def MIN_MARIADB_SERVER_VERSION +* Minimum MariaDB server (version triplet after the 5.5.5- compatibility prefix) +*/ +#define MIN_MARIADB_SERVER_VERSION "10.4.0" + template class ProducerConsumerQueue; diff --git a/src/server/database/Database/MySQLConnection.cpp b/src/server/database/Database/MySQLConnection.cpp index 48c9c4a..f77565a 100644 --- a/src/server/database/Database/MySQLConnection.cpp +++ b/src/server/database/Database/MySQLConnection.cpp @@ -129,13 +129,17 @@ uint32 MySQLConnection::Open() if (m_connectionInfo.ssl != "") { +#ifdef MARIADB_VERSION_ID + my_bool const ssl_enforce = (m_connectionInfo.ssl == "ssl") ? 1 : 0; + if (ssl_enforce) + mysql_options(mysqlInit, MYSQL_OPT_SSL_ENFORCE, &ssl_enforce); +#else mysql_ssl_mode opt_use_ssl = SSL_MODE_DISABLED; if (m_connectionInfo.ssl == "ssl") - { opt_use_ssl = SSL_MODE_REQUIRED; - } mysql_options(mysqlInit, MYSQL_OPT_SSL_MODE, (char const*)&opt_use_ssl); +#endif } m_Mysql = reinterpret_cast(mysql_real_connect(mysqlInit, m_connectionInfo.host.c_str(), m_connectionInfo.user.c_str(), @@ -216,7 +220,7 @@ bool MySQLConnection::Execute(PreparedStatementBase* stmt) uint32 _s = getMSTime(); -#if MYSQL_VERSION_ID >= 80300 +#if MYSQL_VERSION_ID >= 80300 && !defined(MARIADB_VERSION_ID) if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr)) #else if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) @@ -268,7 +272,7 @@ bool MySQLConnection::_Query(PreparedStatementBase* stmt, MySQLPreparedStatement uint32 _s = getMSTime(); -#if MYSQL_VERSION_ID >= 80300 +#if MYSQL_VERSION_ID >= 80300 && !defined(MARIADB_VERSION_ID) if (mysql_stmt_bind_named_param(msql_STMT, msql_BIND, m_mStmt->GetParameterCount(), nullptr)) #else if (mysql_stmt_bind_param(msql_STMT, msql_BIND)) diff --git a/src/server/scripts/Custom/custom_script_loader.cpp b/src/server/scripts/Custom/custom_script_loader.cpp new file mode 100644 index 0000000..8f381e9 --- /dev/null +++ b/src/server/scripts/Custom/custom_script_loader.cpp @@ -0,0 +1,26 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +// This is where scripts' loading functions should be declared: +// void MyExampleScript() + +// The name of this function should match: +// void Add${NameOfDirectory}Scripts() +void AddCustomScripts() +{ + // MyExampleScript() +}