278 lines
7.2 KiB
C++
Raw Normal View History

#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 操作失败");
}