From f54f3317bde4f9a50f6410a866f7fc9b7f9036e2 Mon Sep 17 00:00:00 2001 From: HeZongLun <13425468+hezonglun@user.noreply.gitee.com> Date: Sat, 1 Nov 2025 20:43:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=BA=86=20ODBC=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E4=BD=BF=E5=85=B6=E5=8F=AF=E4=BB=A5=E8=B7=A8?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NahidaProject.Database/Sources/ODBC.cpp | 290 ++++++++++++++---- NahidaProject.Database/Sources/ODBC.h | 158 ++++++++-- .../Sources/ODBCConnection.cpp | 284 ----------------- .../Sources/ODBCConnection.h | 68 ---- .../Tests/ModuleUnitTestFile.cpp | 58 +++- 5 files changed, 395 insertions(+), 463 deletions(-) delete mode 100644 NahidaProject.Database/Sources/ODBCConnection.cpp delete mode 100644 NahidaProject.Database/Sources/ODBCConnection.h diff --git a/NahidaProject.Database/Sources/ODBC.cpp b/NahidaProject.Database/Sources/ODBC.cpp index f218035..6db7778 100644 --- a/NahidaProject.Database/Sources/ODBC.cpp +++ b/NahidaProject.Database/Sources/ODBC.cpp @@ -1,87 +1,251 @@ -/* -Copyright (c) 2025 HeZongLun -NahidaProject is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan -PSL v2. -You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. -*/ - #include "ODBC.h" -#include -int NahidaProject::ODBC::Connect(SQLCHAR* datasource, SQLCHAR* username, SQLCHAR* password) { - SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv); - SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER); - ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); - ret = SQLConnect(this->hdbc, datasource, SQL_NTS, username, SQL_NTS, password, SQL_NTS); +// 构造连接字符串 +std::string NahidaProject::ConnectionInfo::ToConnectionString() const { + std::string connectString; + +#ifdef _WIN32 + if (!driver.empty()) { + connectString += "DRIVER={" + driver + "};"; + } + if (!server.empty()) { + connectString += "SERVER=" + server + ";"; + } + if (port > 0) { + connectString += "PORT=" + std::to_string(port) + ";"; + } + if (!database.empty()) { + connectString += "DATABASE=" + database + ";"; + } + if (!username.empty()) { + connectString += "UID=" + username + ";"; + } + if (!password.empty()) { + connectString += "PWD=" + password + ";"; + } +#else + // Linux/macOS 使用不同的格式 + if (!driver.empty()) { + connectString += "Driver={" + driver + "};"; + } + if (!server.empty()) { + connectString += "Server=" + server + ";"; + } + if (port > 0) { + connectString += "Port=" + std::to_string(port) + ";"; + } + if (!database.empty()) { + connectString += "Database=" + database + ";"; + } + if (!username.empty()) { + connectString += "Uid=" + username + ";"; + } + if (!password.empty()) { + connectString += "Pwd=" + password + ";"; + } +#endif + + return connectString; +} + +void NahidaProject::RowData::AddColumn(const std::string& name, const std::string& value) { + columnNames.push_back(name); + data[name] = value; +} + +std::string NahidaProject::RowData::Get(const std::string& columnName) const { + auto it = data.find(columnName); + return (it != data.end()) ? it->second : ""; +} - return ret; +bool NahidaProject::RowData::HasColumn(const std::string& columnName) const { + return data.find(columnName) != data.end(); } -void NahidaProject::ODBC::RegisterTransaction(std::string transactionName, std::function transactionFunction){ - this->transactionList.insert(std::pair>(transactionName, transactionFunction)); +size_t NahidaProject::RowData::GetColumnCount() const { + return columnNames.size(); } -void NahidaProject::ODBC::InvokeTransaction(std::string transactionName){ - ExecuteSQLWithoutResult((SQLCHAR*)"BEGIN TRANSACTION;"); - this->transactionList[transactionName](); - ExecuteSQLWithoutResult((SQLCHAR*)"COMMITE TRANSACTION;"); +const std::vector& NahidaProject::RowData::GetColumnNames() const { + return columnNames; } -void NahidaProject::ODBC::UndoTransaction(){ - ExecuteSQLWithoutResult((SQLCHAR*)"ROLLBACK TRANSACTION;"); +const std::map& NahidaProject::RowData::GetData() const { + return data; } -void NahidaProject::ODBC::DeleteTransaction(std::string transactionName){ - this->transactionList.erase(transactionName); +void NahidaProject::Connection::CheckError(SQLRETURN ret, SQLHANDLE handle, SQLSMALLINT type, const std::string& operation) { + if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { + return; + } + + SQLCHAR sqlState[6]; + SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER nativeError; + SQLSMALLINT messageLength; + + SQLGetDiagRec(type, handle, 1, sqlState, &nativeError, message, sizeof(message), &messageLength); + + std::string errorMessage = "ODBC Error in " + operation + ": " + std::string(reinterpret_cast(message)); + throw ODBCException(errorMessage, ret); } -int NahidaProject::ODBC::ExecuteSQLWithoutResult(SQLCHAR* sqlStatement) { - ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); - ret = SQLExecDirect(hstmt, sqlStatement, SQL_NTS); - SQLCancel(hstmt); - return ret; +void NahidaProject::Connection::Connect(const ConnectionInfo& info) { + if (isConnected) { + throw ODBCException("Already connected to database"); + } + + std::string connectString = info.ToConnectionString(); + SQLCHAR* connectStringPoint = reinterpret_cast(const_cast(connectString.c_str())); + SQLSMALLINT connectStringLength = static_cast(connectString.length()); + + SQLRETURN ret = SQLDriverConnect(handleOfDatabaseConnect, nullptr, connectStringPoint, connectStringLength, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + + CheckError(ret, handleOfDatabaseConnect, SQL_HANDLE_DBC, "SQLDriverConnect"); + isConnected = true; } -std::vector> NahidaProject::ODBC::ExecuteSQLWithResult(SQLCHAR* sqlStatement) { - std::vector> results; - - ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); - ret = SQLExecDirect(hstmt, sqlStatement, SQL_NTS); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO){ - return { }; +void NahidaProject::Connection::Connect(const std::string& connectionString) { + if (isConnected) { + throw ODBCException("Already connected to database"); } - SQLCHAR midData[MAXCHAR] = { 0 }; - SQLLEN midDataLength = 0; - SQLSMALLINT columnCount = 0; - SQLNumResultCols(hstmt, &columnCount); + SQLCHAR* connectStringPoint = reinterpret_cast(const_cast(connectionString.c_str())); + SQLSMALLINT connectStringLength = static_cast(connectionString.length()); + + SQLRETURN ret = SQLDriverConnect(handleOfDatabaseConnect, nullptr, connectStringPoint, connectStringLength, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); + + CheckError(ret, handleOfDatabaseConnect, SQL_HANDLE_DBC, "SQLDriverConnect"); + isConnected = true; +} + +void NahidaProject::Connection::Disconnect() { + if (isConnected && handleOfDatabaseConnect != SQL_NULL_HANDLE) { + SQLDisconnect(handleOfDatabaseConnect); + isConnected = false; + } +} + +bool NahidaProject::Connection::IsConnected() const { + return isConnected; +} + +void NahidaProject::Statement::CheckError(SQLRETURN ret, const std::string& operation) { + if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { + return; + } - int line = 0; - while (SQLFetch(hstmt) != SQL_NO_DATA_FOUND) { - std::vector result; - ++line; - for (int col = 1; col <= columnCount; ++col){ - midData[0] = 0; - ret = SQLGetData(hstmt, col, SQL_C_CHAR, midData, MAXCHAR, &midDataLength); - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO){ - result.push_back((const char*)(char*)midData); - } + SQLCHAR sqlState[6] = ""; + SQLCHAR message[SQL_MAX_MESSAGE_LENGTH] = ""; + SQLINTEGER nativeError; + SQLSMALLINT messageLength; + + SQLGetDiagRec(SQL_HANDLE_STMT, handleOfStatement, 1, sqlState, &nativeError, message, sizeof(message), &messageLength); + + std::string errorMessage = "Statement Error in " + operation + ": " + std::string(reinterpret_cast(message)); + throw ODBCException(errorMessage, ret); +} + +std::string NahidaProject::Statement::GetColumnData(SQLSMALLINT columnNumber) { + SQLCHAR buffer[1024] = { 0 }; + SQLLEN indicator; + + SQLRETURN ret = SQLGetData(handleOfStatement, columnNumber, SQL_C_CHAR, buffer, sizeof(buffer), &indicator); + + if (ret == SQL_ERROR) { + CheckError(ret, "SQLGetData"); + } + + if (indicator == SQL_NULL_DATA) { + return ""; + } + + return std::string(reinterpret_cast(buffer)); +} + +int NahidaProject::Statement::Execute(const std::string& sql) { + SQLCHAR* sqlPoint = reinterpret_cast(const_cast(sql.c_str())); + + SQLRETURN ret = SQLExecDirect(handleOfStatement, sqlPoint, SQL_NTS); + CheckError(ret, "SQLExecDirect"); + + SQLLEN rowCount; + ret = SQLRowCount(handleOfStatement, &rowCount); + CheckError(ret, "SQLRowCount"); + + return static_cast(rowCount); +} + +// 执行查询语句(SELECT) +std::vector NahidaProject::Statement::Query(const std::string& sql) { + SQLCHAR* sqlPoint = reinterpret_cast(const_cast(sql.c_str())); + + SQLRETURN ret = SQLExecDirect(handleOfStatement, sqlPoint, SQL_NTS); + CheckError(ret, "SQLExecDirect"); + + // 获取列信息 + SQLSMALLINT columnCount; + ret = SQLNumResultCols(handleOfStatement, &columnCount); + CheckError(ret, "SQLNumResultCols"); + + // 获取列名 + std::vector columnNames(columnCount); + for (SQLSMALLINT i = 1; i <= columnCount; ++i) { + SQLCHAR columnName[256]; + SQLSMALLINT nameLength; + SQLSMALLINT dataType; + SQLULEN columnSize; + SQLSMALLINT decimalDigits; + SQLSMALLINT nullable; + + ret = SQLDescribeCol(handleOfStatement, i, columnName, sizeof(columnName), &nameLength, &dataType, &columnSize, &decimalDigits, &nullable); + CheckError(ret, "SQLDescribeCol"); + + columnNames[i - 1] = std::string(reinterpret_cast(columnName)); + } + + // 获取结果数据 + std::vector results; + while ((ret = SQLFetch(handleOfStatement)) != SQL_NO_DATA) { + CheckError(ret, "SQLFetch"); + + RowData row; + for (SQLSMALLINT i = 1; i <= columnCount; ++i) { + std::string value = GetColumnData(i); + row.AddColumn(columnNames[i - 1], value); } - results.push_back(result); + results.push_back(row); } - SQLCancel(hstmt); + return results; } -void NahidaProject::ODBC::Close() { - this->transactionList.clear(); - SQLFreeHandle(SQL_HANDLE_STMT, hstmt); - SQLFreeHandle(SQL_HANDLE_DBC, hdbc); - SQLFreeHandle(SQL_HANDLE_ENV, henv); +void NahidaProject::Database::Connect(const ConnectionInfo& info) { + connection->Connect(info); +} + +void NahidaProject::Database::Connect(const std::string& connectionString) { + connection->Connect(connectionString); } + +void NahidaProject::Database::Disconnect() { + connection->Disconnect(); +} + +bool NahidaProject::Database::IsConnected() const { + return connection->IsConnected(); +} + +std::vector NahidaProject::Database::Query(const std::string& sql) { + Statement statement(*connection); + return statement.Query(sql); +} + +int NahidaProject::Database::Execute(const std::string& sql) { + Statement statement(*connection); + return statement.Execute(sql); +} + +std::unique_ptr NahidaProject::Database::CreateStatement() { + return std::make_unique(*connection); +} \ No newline at end of file diff --git a/NahidaProject.Database/Sources/ODBC.h b/NahidaProject.Database/Sources/ODBC.h index 2219fa7..ea72516 100644 --- a/NahidaProject.Database/Sources/ODBC.h +++ b/NahidaProject.Database/Sources/ODBC.h @@ -1,48 +1,142 @@ -/* -Copyright (c) 2025 HeZongLun -NahidaProject is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan -PSL v2. -You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. -*/ #pragma once -#include -#include -#include +#include #include #include -#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif +#include +#include namespace NahidaProject { - class __declspec(dllexport) ODBC { + class ODBCException : public std::runtime_error { + public: + explicit ODBCException(const std::string& message) : std::runtime_error(message) { + + } + + ODBCException(const std::string& message, SQLRETURN ret) : std::runtime_error(message + " (SQL Return Code: " + std::to_string(ret) + ")") { + + } + }; + + struct ConnectionInfo { + std::string server; + std::string database; + std::string username; + std::string password; + std::string driver; + int port = 0; + + std::string ToConnectionString() const; + }; + + class RowData { private: - SQLRETURN ret = NULL; - SQLHENV henv = NULL; - SQLHDBC hdbc = NULL; - SQLHSTMT hstmt = NULL; + std::vector columnNames; + std::map data; - std::map> transactionList; public: - ODBC() { + RowData() = default; + + void AddColumn(const std::string& name, const std::string& value); + std::string Get(const std::string& columnName) const; + bool HasColumn(const std::string& columnName) const; + size_t GetColumnCount() const; + const std::vector& GetColumnNames() const; + const std::map& GetData() const; + }; + + class Connection { + private: + SQLHENV handleOfEnvironment = SQL_NULL_HANDLE; + SQLHDBC handleOfDatabaseConnect = SQL_NULL_HANDLE; + bool isConnected = false; + + void CheckError(SQLRETURN ret, SQLHANDLE handle, SQLSMALLINT type, const std::string& operation); + public: + Connection() { + // 分配环境句柄 + SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handleOfEnvironment); + CheckError(ret, SQL_NULL_HANDLE, SQL_HANDLE_ENV, "SQLAllocHandle(ENV)"); + + // 设置 ODBC 版本为 3.x + ret = SQLSetEnvAttr(handleOfEnvironment, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + CheckError(ret, handleOfEnvironment, SQL_HANDLE_ENV, "SQLSetEnvAttr"); + + // 分配连接句柄 + ret = SQLAllocHandle(SQL_HANDLE_DBC, handleOfEnvironment, &handleOfDatabaseConnect); + CheckError(ret, handleOfEnvironment, SQL_HANDLE_ENV, "SQLAllocHandle(DBC)"); + } + + ~Connection() { + Disconnect(); + + if (handleOfDatabaseConnect != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_DBC, handleOfDatabaseConnect); + } + if (handleOfEnvironment != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_ENV, handleOfEnvironment); + } } - ~ODBC() { + void Connect(const ConnectionInfo& info); + void Connect(const std::string& connectionString); + void Disconnect(); + bool IsConnected() const; + + friend class Statement; + }; + + class Statement { + private: + SQLHSTMT handleOfStatement = SQL_NULL_HANDLE; + Connection* connection = nullptr; + + void CheckError(SQLRETURN ret, const std::string& operation); + std::string GetColumnData(SQLSMALLINT columnNumber); + public: + explicit Statement(Connection& connect) : connection(&connect) { + if (!connect.IsConnected()) { + throw ODBCException("Connection is not established"); + } + + SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, connect.handleOfDatabaseConnect, &handleOfStatement); + if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { + throw ODBCException("Failed to allocate statement handle", ret); + } + } + + ~Statement() { + if (handleOfStatement != SQL_NULL_HANDLE) { + SQLFreeHandle(SQL_HANDLE_STMT, handleOfStatement); + } } - int Connect(SQLCHAR* datasource, SQLCHAR* username, SQLCHAR* password); - void RegisterTransaction(std::string transactionName, std::function transactionFunction); - void InvokeTransaction(std::string transactionName); - void UndoTransaction(); - void DeleteTransaction(std::string transactionName); - int ExecuteSQLWithoutResult(SQLCHAR* sqlStatement); - std::vector> ExecuteSQLWithResult(SQLCHAR* sqlStatement); - void Close(); + + int Execute(const std::string& sql); + std::vector Query(const std::string& sql); + }; + + class Database { + private: + std::unique_ptr connection; + + public: + Database() : connection(std::make_unique()) {} + + void Connect(const ConnectionInfo& info); + void Connect(const std::string& connectionString); + void Disconnect(); + bool IsConnected() const; + std::vector Query(const std::string& sql); + int Execute(const std::string& sql); + std::unique_ptr CreateStatement(); }; } diff --git a/NahidaProject.Database/Sources/ODBCConnection.cpp b/NahidaProject.Database/Sources/ODBCConnection.cpp deleted file mode 100644 index f0aa8a0..0000000 --- a/NahidaProject.Database/Sources/ODBCConnection.cpp +++ /dev/null @@ -1,284 +0,0 @@ -#include "ODBCConnection.h" - -#include -#include - -ODBCConnection::ODBCConnection() : environmentHandle(SQL_NULL_HENV), databaseHandle(SQL_NULL_HDBC), connected(false) { - Initialize(); -} - -ODBCConnection::~ODBCConnection() { - if (connected) { - Disconnect(); - } - CleanUp(); -} - -void ODBCConnection::Initialize() { - SQLRETURN ret; - - ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &environmentHandle); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to allocate environment handle"); - } - - ret = SQLSetEnvAttr(environmentHandle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to set ODBC version"); - } - - ret = SQLAllocHandle(SQL_HANDLE_DBC, environmentHandle, &databaseHandle); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to allocate connection handle"); - } -} - -void ODBCConnection::CleanUp() { - if (databaseHandle != SQL_NULL_HDBC) { - SQLFreeHandle(SQL_HANDLE_DBC, databaseHandle); - databaseHandle = SQL_NULL_HDBC; - } - if (environmentHandle != SQL_NULL_HENV) { - SQLFreeHandle(SQL_HANDLE_ENV, environmentHandle); - environmentHandle = SQL_NULL_HENV; - } -} - -void ODBCConnection::Connect(const std::string& dsn, const std::string& username, - const std::string& password) { - if (connected) { - throw ODBCException("Already connected to database"); - } - - SQLRETURN ret; - SQLCHAR outString[1024]; - SQLSMALLINT outStringLength; - - ret = SQLConnect(databaseHandle, (SQLCHAR*)dsn.c_str(), SQL_NTS, (SQLCHAR*)(username.empty() ? nullptr : username.c_str()), SQL_NTS, (SQLCHAR*)(password.empty() ? nullptr : password.c_str()), SQL_NTS); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Connect to database"); - - connected = true; -} - -void ODBCConnection::ConnectWithConnectionString(const std::string& connectionString) { - if (connected) { - throw ODBCException("Already connected to database"); - } - - SQLRETURN ret; - - ret = SQLDriverConnect(databaseHandle, nullptr, (SQLCHAR*)connectionString.c_str(), SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_COMPLETE); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Connect with connection string"); - connected = true; -} - -void ODBCConnection::Disconnect() { - if (connected) { - SQLDisconnect(databaseHandle); - connected = false; - } -} - -bool ODBCConnection::IsConnected() const { - return connected; -} - -void ODBCConnection::CheckError(SQLRETURN ret, SQLHANDLE handle, SQLSMALLINT type, - const std::string& operation) { - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { - return; - } - - SQLCHAR sqlState[6]; - SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; - SQLINTEGER nativeError; - SQLSMALLINT messageLength; - - SQLGetDiagRec(type, handle, 1, sqlState, &nativeError, message, sizeof(message), &messageLength); - - std::string errorMessage = "ODBC Error in " + operation + ": " + std::string((char*)message) + " (SQL State: " + std::string((char*)sqlState) + ")"; - throw ODBCException(errorMessage); -} - -void ODBCConnection::Execute(const std::string& sql) { - if (!connected) { - throw ODBCException("Not connected to database"); - } - - SQLHSTMT statement; - SQLRETURN ret; - - ret = SQLAllocHandle(SQL_HANDLE_STMT, databaseHandle, &statement); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to allocate statement handle"); - } - - ret = SQLExecDirect(statement, (SQLCHAR*)sql.c_str(), SQL_NTS); - CheckError(ret, statement, SQL_HANDLE_STMT, "Execute SQL"); - - SQLFreeHandle(SQL_HANDLE_STMT, statement); -} - -std::unique_ptr ODBCConnection::Query(const std::string& sql) { - if (!connected) { - throw ODBCException("Not connected to database"); - } - - SQLHSTMT statement; - SQLRETURN ret; - - ret = SQLAllocHandle(SQL_HANDLE_STMT, databaseHandle, &statement); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to allocate statement handle"); - } - - ret = SQLExecDirect(statement, (SQLCHAR*)sql.c_str(), SQL_NTS); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - SQLFreeHandle(SQL_HANDLE_STMT, statement); - CheckError(ret, statement, SQL_HANDLE_STMT, "Execute query"); - } - - return std::make_unique(statement); -} - -void ODBCConnection::BeginTransaction() { - if (!connected) { - throw ODBCException("Not connected to database"); - } - - SQLRETURN ret = SQLSetConnectAttr(databaseHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Begin transaction"); -} - -void ODBCConnection::Commit() { - if (!connected) { - throw ODBCException("Not connected to database"); - } - - SQLRETURN ret = SQLEndTran(SQL_HANDLE_DBC, databaseHandle, SQL_COMMIT); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Commit transaction"); - - ret = SQLSetConnectAttr(databaseHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, 0); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Enable autocommit"); -} - -void ODBCConnection::Rollback() { - if (!connected) { - throw ODBCException("Not connected to database"); - } - - SQLRETURN ret = SQLEndTran(SQL_HANDLE_DBC, databaseHandle, SQL_ROLLBACK); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Rollback transaction"); - - ret = SQLSetConnectAttr(databaseHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, 0); - CheckError(ret, databaseHandle, SQL_HANDLE_DBC, "Enable autocommit"); -} - -std::string ODBCConnection::GetLastError() const { - SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; - SQLCHAR sqlState[6]; - SQLINTEGER nativeError; - SQLSMALLINT messageLength; - - SQLRETURN ret = SQLGetDiagRec(SQL_HANDLE_DBC, databaseHandle, 1, sqlState, &nativeError, message, sizeof(message), &messageLength); - - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { - return std::string((char*)message); - } - - return "No error information available"; -} - -ODBCConnection::ResultSet::ResultSet(SQLHSTMT statementHandle) : statementHandle(statementHandle) { - SQLRETURN ret; - - ret = SQLNumResultCols(statementHandle, &columnCount); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to get column count"); - } - - columnData.resize(columnCount); - columnIndicators.resize(columnCount); - - for (SQLSMALLINT i = 0; i < columnCount; i++) { - SQLSMALLINT columnNameLength; - SQLSMALLINT dataType; - SQLULEN columnSize; - SQLSMALLINT decimalDigits; - SQLSMALLINT nullable; - - ret = SQLDescribeCol(statementHandle, i + 1, nullptr, 0, &columnNameLength, &dataType, &columnSize, &decimalDigits, &nullable); - - columnData[i] = new SQLCHAR[1024]; - ret = SQLBindCol(statementHandle, i + 1, SQL_C_CHAR, columnData[i], 1024,&columnIndicators[i]); - if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { - throw ODBCException("Failed to bind column"); - } - } -} - -ODBCConnection::ResultSet::~ResultSet() { - for (auto& data : columnData) { - delete[] data; - } - if (statementHandle != SQL_NULL_HSTMT) { - SQLFreeHandle(SQL_HANDLE_STMT, statementHandle); - } -} - -bool ODBCConnection::ResultSet::Next() { - SQLRETURN ret = SQLFetch(statementHandle); - if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { - return true; - } - else if (ret == SQL_NO_DATA) { - return false; - } - else { - throw ODBCException("Failed to fetch next row"); - } -} - -std::string ODBCConnection::ResultSet::GetString(int column) { - if (column < 1 || column > columnCount) { - throw ODBCException("Invalid column index"); - } - - if (IsNull(column)) { - return ""; - } - - return std::string((char*)columnData[column - 1]); -} - -int ODBCConnection::ResultSet::GetInteger(int column) { - if (column < 1 || column > columnCount) { - throw ODBCException("Invalid column index"); - } - - if (IsNull(column)) { - return 0; - } - - return std::stoi((char*)columnData[column - 1]); -} - -double ODBCConnection::ResultSet::GetDouble(int column) { - if (column < 1 || column > columnCount) { - throw ODBCException("Invalid column index"); - } - - if (IsNull(column)) { - return 0.0; - } - - return std::stod((char*)columnData[column - 1]); -} - -bool ODBCConnection::ResultSet::IsNull(int column) { - if (column < 1 || column > columnCount) { - throw ODBCException("Invalid column index"); - } - - return columnIndicators[column - 1] == SQL_NULL_DATA; -} \ No newline at end of file diff --git a/NahidaProject.Database/Sources/ODBCConnection.h b/NahidaProject.Database/Sources/ODBCConnection.h deleted file mode 100644 index e207e3b..0000000 --- a/NahidaProject.Database/Sources/ODBCConnection.h +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#include -#include - -class ODBCException : public std::runtime_error { -public: - explicit ODBCException(const std::string& message) : std::runtime_error(message) {} -}; - -class ODBCConnection { -public: - ODBCConnection(); - ~ODBCConnection(); - - ODBCConnection(const ODBCConnection&) = delete; - ODBCConnection& operator=(const ODBCConnection&) = delete; - - void Connect(const std::string& dsn, const std::string& username = "", const std::string& password = ""); - void ConnectWithConnectionString(const std::string& connectionString); - void Disconnect(); - bool IsConnected() const; - - void Execute(const std::string& sql); - - class ResultSet { - public: - ResultSet(SQLHSTMT stmt); - ~ResultSet(); - - bool Next(); - std::string GetString(int column); - int GetInteger(int column); - double GetDouble(int column); - bool IsNull(int column); - - private: - SQLHSTMT statementHandle; - SQLSMALLINT columnCount; - std::vector columnData; - std::vector columnIndicators; - }; - - std::unique_ptr Query(const std::string& sql); - - void BeginTransaction(); - void Commit(); - void Rollback(); - - std::string GetLastError() const; - -private: - SQLHENV environmentHandle; - SQLHDBC databaseHandle; - bool connected; - - void CheckError(SQLRETURN ret, SQLHANDLE handle, SQLSMALLINT type, const std::string& operation); - void Initialize(); - void CleanUp(); -}; diff --git a/NahidaProject.Database/Tests/ModuleUnitTestFile.cpp b/NahidaProject.Database/Tests/ModuleUnitTestFile.cpp index fedab34..14f8355 100644 --- a/NahidaProject.Database/Tests/ModuleUnitTestFile.cpp +++ b/NahidaProject.Database/Tests/ModuleUnitTestFile.cpp @@ -3,25 +3,51 @@ #include "..\Sources\ODBC.h" Test(ODBCTest) { - NahidaProject::ODBC odbc; - std::cout << odbc.Connect((SQLCHAR*)"SQL Server Datasource", (SQLCHAR*)"sa", (SQLCHAR*)"081105") << std::endl; - std::cout << odbc.ExecuteSQLWithoutResult((SQLCHAR*)"SELECT @@VERSION;") << std::endl; - std::vector> a = odbc.ExecuteSQLWithResult((SQLCHAR*)"SELECT * FROM Northwind.dbo.Products;"); - - for (std::vector item1 : a) { - for (std::string item2 : item1) { - std::cout << item2 << std::endl; - } - } + try { + NahidaProject::Database database; + + // 方式1:使用 ConnectionInfo 结构体 + NahidaProject::ConnectionInfo info; + info.driver = "SQL Server"; // 根据实际驱动调整 + info.server = "localhost"; + info.database = "TestDatabase"; + info.username = "sa"; + info.password = "081105"; + info.port = 3306; - odbc.RegisterTransaction("Transaction1", [&]() { - odbc.ExecuteSQLWithoutResult((SQLCHAR*)"DROP TABLE dbo.Products;"); - }); + database.Connect(info); - odbc.InvokeTransaction("Transaction1"); - odbc.UndoTransaction(); + // 或者方式2:直接使用连接字符串 + // db.connect("DRIVER={MySQL ODBC 8.0 Driver};SERVER=localhost;DATABASE=testdb;UID=root;PWD=password;"); - odbc.Close(); + std::cout << "Connected successfully!" << std::endl; + + // 执行查询 + auto results = database.Query("SELECT @@VERSION"); + + for (const auto& row : results) { + for (const auto& columnName : row.GetColumnNames()) { + std::cout << columnName << "=" << row.Get(columnName) << " "; + } + std::cout << std::endl; + } + + /* + int affectedRows = db.execute("INSERT INTO UserTable (name, email) VALUES ('John Doe', 'john@example.com')"); + std::cout << "Inserted " << affectedRows << " rows" << std::endl; + + // 使用预处理语句 + auto stmt = db.createStatement(); + std::cout << "Prepared insert affected " << affectedRows << " rows" << std::endl; + */ + database.Disconnect(); + } + catch (const NahidaProject::ODBCException& e) { + std::cerr << "ODBC Error: " << e.what() << std::endl; + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + } } int main() { -- Gitee