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.
This commit is contained in:
Dawnforger
2026-05-08 20:11:43 -05:00
parent 22e79a4f32
commit 2f8c374569
6 changed files with 107 additions and 27 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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)
@@ -31,6 +31,7 @@
#include "SQLOperation.h"
#include "Transaction.h"
#include "WorldDatabase.h"
#include <array>
#include <limits>
#include <mysqld_error.h>
#include <sstream>
@@ -59,10 +60,18 @@ DatabaseWorkerPool<T>::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<T>::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<unsigned, 3>
{
std::vector<uint8> result;
std::istringstream parser(input);
result.push_back(parser.get());
for (int i = 1; i < 3; i++)
std::array<unsigned, 3> 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<unsigned>(std::stoul(num));
num.clear();
}
}
else
break;
}
return result;
if (!num.empty() && idx < 3)
v[idx] = static_cast<unsigned>(std::stoul(num));
return v;
};
// default to values for MySQL
uint8 offset = 0;
auto compareVersion = [](std::array<unsigned, 3> const& a, std::array<unsigned, 3> 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 <class T>
@@ -442,7 +479,7 @@ uint32 DatabaseWorkerPool<T>::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;
}
@@ -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 <typename T>
class ProducerConsumerQueue;
@@ -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<MySQLHandle*>(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))
@@ -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 <http://www.gnu.org/licenses/>.
*/
// 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()
}