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