278 lines
7.2 KiB
C++
278 lines
7.2 KiB
C++
#include "MariaDbClient.h"
|
||
|
||
#include <QtGlobal>
|
||
|
||
#ifdef Q_OS_WIN
|
||
#include <windows.h>
|
||
#else
|
||
#include <dlfcn.h>
|
||
#endif
|
||
|
||
namespace {
|
||
|
||
void* LoadNativeLibrary(const char* name)
|
||
{
|
||
#ifdef Q_OS_WIN
|
||
return static_cast<void*>(LoadLibraryA(name));
|
||
#else
|
||
return dlopen(name, RTLD_NOW | RTLD_GLOBAL);
|
||
#endif
|
||
}
|
||
|
||
void CloseNativeLibrary(void* handle)
|
||
{
|
||
if (!handle) {
|
||
return;
|
||
}
|
||
#ifdef Q_OS_WIN
|
||
FreeLibrary(static_cast<HMODULE>(handle));
|
||
#else
|
||
dlclose(handle);
|
||
#endif
|
||
}
|
||
|
||
void* ResolveSymbol(void* handle, const char* name)
|
||
{
|
||
#ifdef Q_OS_WIN
|
||
return static_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name));
|
||
#else
|
||
return dlsym(handle, name);
|
||
#endif
|
||
}
|
||
|
||
} // namespace
|
||
|
||
MariaDbClient::MariaDbClient()
|
||
{
|
||
LoadClientLibrary();
|
||
}
|
||
|
||
MariaDbClient::~MariaDbClient()
|
||
{
|
||
Close();
|
||
if (m_library) {
|
||
CloseNativeLibrary(m_library);
|
||
m_library = nullptr;
|
||
}
|
||
}
|
||
|
||
bool MariaDbClient::IsLibraryAvailable() const
|
||
{
|
||
return m_library != nullptr && m_init != nullptr && m_realConnect != nullptr;
|
||
}
|
||
|
||
bool MariaDbClient::IsConnected() const
|
||
{
|
||
return m_connected && m_mysql != nullptr;
|
||
}
|
||
|
||
QString MariaDbClient::LastError() const
|
||
{
|
||
return m_lastError;
|
||
}
|
||
|
||
bool MariaDbClient::LoadClientLibrary()
|
||
{
|
||
if (m_library && m_init) {
|
||
return true;
|
||
}
|
||
|
||
const char* names[] = {
|
||
#ifdef Q_OS_WIN
|
||
"libmariadb.dll",
|
||
"libmysql.dll",
|
||
#else
|
||
"libmariadb.so.3",
|
||
"libmariadb.so",
|
||
"libmysqlclient.so.21",
|
||
"libmysqlclient.so",
|
||
#endif
|
||
nullptr
|
||
};
|
||
for (int i = 0; names[i]; ++i) {
|
||
m_library = LoadNativeLibrary(names[i]);
|
||
if (m_library) {
|
||
break;
|
||
}
|
||
}
|
||
if (!m_library) {
|
||
SetError(QStringLiteral("未找到 libmariadb,请先安装离线 MariaDB 包"));
|
||
return false;
|
||
}
|
||
if (!BindSymbols()) {
|
||
CloseNativeLibrary(m_library);
|
||
m_library = nullptr;
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool MariaDbClient::BindSymbols()
|
||
{
|
||
m_init = reinterpret_cast<void* (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_init"));
|
||
m_realConnect = reinterpret_cast<void* (*)(void*,
|
||
const char*,
|
||
const char*,
|
||
const char*,
|
||
const char*,
|
||
unsigned int,
|
||
const char*,
|
||
unsigned long)>(
|
||
ResolveSymbol(m_library, "mysql_real_connect"));
|
||
m_query = reinterpret_cast<int (*)(void*, const char*)>(
|
||
ResolveSymbol(m_library, "mysql_query"));
|
||
m_storeResult = reinterpret_cast<void* (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_store_result"));
|
||
m_freeResult = reinterpret_cast<void (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_free_result"));
|
||
m_fetchRow = reinterpret_cast<char** (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_fetch_row"));
|
||
m_numFields = reinterpret_cast<unsigned int (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_num_fields"));
|
||
m_error = reinterpret_cast<const char* (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_error"));
|
||
m_close = reinterpret_cast<void (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_close"));
|
||
m_insertId = reinterpret_cast<unsigned long long (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_insert_id"));
|
||
m_fieldCount = reinterpret_cast<unsigned int (*)(void*)>(
|
||
ResolveSymbol(m_library, "mysql_field_count"));
|
||
m_setCharacterSet = reinterpret_cast<int (*)(void*, const char*)>(
|
||
ResolveSymbol(m_library, "mysql_set_character_set"));
|
||
if (!m_init || !m_realConnect || !m_query || !m_storeResult ||
|
||
!m_freeResult || !m_fetchRow || !m_numFields || !m_error ||
|
||
!m_close || !m_insertId || !m_fieldCount) {
|
||
SetError(QStringLiteral("libmariadb 符号不完整"));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool MariaDbClient::Connect(const QString& host,
|
||
int port,
|
||
const QString& user,
|
||
const QString& password,
|
||
const QString& database)
|
||
{
|
||
Close();
|
||
if (!LoadClientLibrary()) {
|
||
return false;
|
||
}
|
||
|
||
m_mysql = m_init(nullptr);
|
||
if (!m_mysql) {
|
||
SetError(QStringLiteral("mysql_init 失败"));
|
||
return false;
|
||
}
|
||
|
||
const QByteArray hostUtf8 = host.toUtf8();
|
||
const QByteArray userUtf8 = user.toUtf8();
|
||
const QByteArray passwordUtf8 = password.toUtf8();
|
||
const QByteArray databaseUtf8 = database.toUtf8();
|
||
// 连接失败时不影响引导主链路,调用方只记日志。
|
||
if (!m_realConnect(m_mysql,
|
||
hostUtf8.constData(),
|
||
userUtf8.constData(),
|
||
passwordUtf8.constData(),
|
||
databaseUtf8.constData(),
|
||
static_cast<unsigned int>(port),
|
||
nullptr,
|
||
0)) {
|
||
SetError(ConnectionError());
|
||
m_close(m_mysql);
|
||
m_mysql = nullptr;
|
||
return false;
|
||
}
|
||
if (m_setCharacterSet) {
|
||
m_setCharacterSet(m_mysql, "utf8mb4");
|
||
}
|
||
m_connected = true;
|
||
m_lastError.clear();
|
||
return true;
|
||
}
|
||
|
||
void MariaDbClient::Close()
|
||
{
|
||
if (m_mysql && m_close) {
|
||
m_close(m_mysql);
|
||
}
|
||
m_mysql = nullptr;
|
||
m_connected = false;
|
||
}
|
||
|
||
bool MariaDbClient::Exec(const QString& sql)
|
||
{
|
||
ResultSet unused;
|
||
return Query(sql, unused);
|
||
}
|
||
|
||
bool MariaDbClient::Query(const QString& sql, ResultSet& rows)
|
||
{
|
||
rows.clear();
|
||
if (!IsConnected()) {
|
||
SetError(QStringLiteral("数据库未连接"));
|
||
return false;
|
||
}
|
||
|
||
const QByteArray sqlUtf8 = sql.toUtf8();
|
||
if (m_query(m_mysql, sqlUtf8.constData()) != 0) {
|
||
SetError(ConnectionError());
|
||
return false;
|
||
}
|
||
|
||
void* result = m_storeResult(m_mysql);
|
||
if (!result) {
|
||
if (m_fieldCount(m_mysql) != 0) {
|
||
SetError(ConnectionError());
|
||
return false;
|
||
}
|
||
m_lastError.clear();
|
||
return true;
|
||
}
|
||
|
||
const unsigned int fieldCount = m_numFields(result);
|
||
while (char** row = m_fetchRow(result)) {
|
||
Row values;
|
||
for (unsigned int i = 0; i < fieldCount; ++i) {
|
||
values.append(row[i] ? QString::fromUtf8(row[i]) : QString());
|
||
}
|
||
rows.append(values);
|
||
}
|
||
m_freeResult(result);
|
||
m_lastError.clear();
|
||
return true;
|
||
}
|
||
|
||
unsigned long long MariaDbClient::LastInsertId() const
|
||
{
|
||
if (!IsConnected() || !m_insertId) {
|
||
return 0;
|
||
}
|
||
return m_insertId(m_mysql);
|
||
}
|
||
|
||
QString MariaDbClient::Escape(const QString& value)
|
||
{
|
||
QString escaped = value;
|
||
escaped.replace(QLatin1Char('\\'), QStringLiteral("\\\\"));
|
||
escaped.replace(QLatin1Char('\''), QStringLiteral("''"));
|
||
return escaped;
|
||
}
|
||
|
||
void MariaDbClient::SetError(const QString& message)
|
||
{
|
||
m_lastError = message;
|
||
}
|
||
|
||
QString MariaDbClient::ConnectionError() const
|
||
{
|
||
if (m_mysql && m_error) {
|
||
const char* text = m_error(m_mysql);
|
||
if (text && text[0] != '\0') {
|
||
return QString::fromUtf8(text);
|
||
}
|
||
}
|
||
return QStringLiteral("MariaDB 操作失败");
|
||
}
|