C++使用SQL Server 二:简易使用
参考官方的实现:基本 ODBC 应用程序 - ODBC API Reference | Microsoft Docs
官方是使用的C语言实现的
我不会C语言,变换了一下,使用c++整了;
代码
类
Link_SQL_Server:负责创建数据源,链接数据源,使用固定sql语句查询操作,获取查询结果
实现
Link_SQL_Server.h

#pragma once #ifndef LINK_SQL_SERVER_H_ #define LINK_SQL_SERVER_H_ #include <windows.h> //位于最上面 #include <odbcinst.h> //windows.h必须位于odbcinst头文件前面,且需要在项目中添加legacy_stdio_definitions.lib #include <iostream> #include <string> #include <sql.h> #include <sqlext.h> #include <stdio.h> #include <conio.h> #include <tchar.h> #include <stdlib.h> #include <sal.h> #include <vector> #include <list> #include <unordered_map> //无序map #include "D:\C++\my_func\my_function.h" class STR_BINDING; class Link_SQL_Server; /* * Link_SQL_Server:用于创建数据源、链接SQL Server数据库,执行SQL语句、获取语句执行结果 */ class Link_SQL_Server { public: Link_SQL_Server() {}; virtual~Link_SQL_Server(); /*创建数据源,可选函数 */ bool CreateDataSource(std::wstring dsn, std::wstring database=L"master", std::wstring server = L"127.0.0.1", std::wstring description = L"", std::wstring trusted_connection = L"no"); /*返回数据库是否连接成功*/ bool get_isLink(); /*通过参数err_infos返回存储错误信息的对象,参数size表示返回最近的几条错误信息;最多返回最近的5条错误信息*/ void get_errInfos(std::wstring* err_infos, unsigned short size = err_array_len); /*直接将最近的错误信息输出到控制台*/ void get_errInfos(); /*连接数据源,使用指定的链接字符串链接SQL Server数据源*/ bool link(const std::wstring& conn_str); /*连接数据源,根据已有的信息链接SQL Server数据源*/ bool link(); /*设置链接账号和密码*/ void set_UID_PWD(const std::wstring& uid, const std::wstring& pwd) { this->UID = uid; this->PWD = pwd; } /*通过控制台读取链接账号和密码*/ void set_UID_PWD(); /*执行sql语句,返回执行代码 * 调用该函数之后,你应该调用get_success函数获取语句是否执行成功 */ RETCODE statementExecution(const std::wstring& sql_str); /* * 执行sql语句,返回执行代码 * 此函数是重载函数,用途为减轻对于变量长度为max的数据项长度在分配内存时申请过多导致的速度缓慢 * 举例:有一个文本数据,在数据库中的变量类型为nvarchar(max),那么在读取的时候,系统会申请1G多的空间用于存放,但是实际上,这个文本的长度远没有那么长。但是数据库规定最长可定义长度为4000。文本可能长于4000。 * 所以可以先通过数据库的LEN函数查询出该项的实际长度,在后续为其分配内存时以该长度为准,避免过多申请内存 * 注意,这个只支持简单的查询语句,对于复杂的查询语句,请不要使用该函数。 * values:字段名数组,里面存储各个需要使用LEN函数的字段名 * value_index:字段是第几列,从1开始。 */ RETCODE statementExecution(const std::wstring& sql_str,const std::vector<std::wstring>values, const std::vector<unsigned>value_index); /*获取sql语句、获取结果等是否成功*/ bool get_success()const { return this->is_success; } /*获取结果*/ const std::list<STR_BINDING>& getRsults() { return this->sql_retValues; } /*获取本次结果应该显示多少列 通过参数将值传递回去 */ unsigned get_Col_num()const; private: std::wstring Driver = L"SQL Server";//SQL Server数据库引擎名称,也就是驱动程序名称;在ODBC管理程序里面可以看到 std::wstring DSN; //数据源名称 std::wstring Database; //SQL Server数据源默认数据库 std::wstring Server; //SQL Server引擎所在的IP地址,一般为localhost、127.0.0.1等 std::wstring Description; //数据源描述 std::wstring Trusted_Connection; //yes:Windows 身份验证模式进行登录验证;no:使用 SQL Server 用户名和密码进行登录验证 std::wstring LinkStatement; //链接语句;即函数SQLConfigDataSource的lpszAttributes参数;注意,LinkStatement存储的字符串并非为最终用于链接函数使用的字符串。 std::wstring UID; //使用 SQL Server 的用户名 std::wstring PWD; //SQL Server 的密码 const static unsigned err_array_len = 5; std::wstring err_infos[err_array_len]{}; //存储错误信息,最多存储5条错误信息,再多的错误信息将被覆盖。 unsigned short errInfo_index{}; //指向err_infos数组的下标,标识错误信息应存储在哪儿。 SQLHENV hEnv = NULL; //环境句柄 SQLHDBC hDbc = NULL; //连接句柄 SQLHSTMT hStmt = NULL; //语句句柄 std::wstring ConnStr; //连接语句 std::wstring wszInput; //sql语句字符串,由用户输入 RETCODE exc_retCode{}; //sql语句执行的返回代码 SQLSMALLINT sNumResults{}; //sql语句执行返回的信息的列数 bool is_success{}; //sql语句是否执行成功,在获取信息的过程中若是失败,那么也是false; bool is_linkSuccess{}; //是否连接数据库成功 std::vector<STR_BINDING> sql_values; //临时存储用,完成结果的存储后需要清空 std::list<STR_BINDING> sql_retValues; //存储最终的结果 std::unordered_map<int, int>index_len; //存储对应字段的下标和对应的最大长度值。 std::vector<unsigned>value_index; //字段的下标,从1开始 protected: /*********************************************************************** 调用ODBC函数并在失败时报告错误。 接受 handle值, handle类型, stmt类型 HandleDiagnosticRecord:显示错误/警告信息;非标准api,而是在本文件中定义的函数 返回值:true表示没有错误信息、false表示存在错误信息 ***********************************************************************/ bool TRYODBC(SQLHENV h, SQLSMALLINT ht, RETCODE x); /************************************************************************ HandleDiagnosticRecord : 显示错误/警告信息 参数: hHandle ODBC 句柄值 hType 句柄类型 (HANDLE_STMT, HANDLE_ENV, HANDLE_DBC) RetCode 失败返回的命令代码 返回值: 返回此次的错误信息 ***********************************************************************/ std::wstring HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode); /*将传递过来的错误信息存储在err_infos数组中,并调整errInfo_index的值*/ void set_errInfo(const std::wstring& errInfo); /*从控制台中输入密码*/ void setpwd(); /*设置给定密码*/ void setpwd(std::wstring&pwd); /*检查语句执行的结果 * 返回true表示sql语句执行没有错误情况,然后调用get_excRsult函数 * 否则你需要调用get_errInfos函数查看错误信息 */ bool check_excRsult(); /*预处理,绑定列缓冲区 */ void pre_allocation_bind(); /* * 获取sql执行的结果,将结果缓存在本地。 * 返回false表示出现错误,请调用get_errInfos * 返回true表示已获取执行结果并缓存在本地了,请调用 查看 */ bool get_excRsult(); /*手动释放缓存的结果*/ bool freedResult(); }; //存储sql语句返回的单个值,并标志该值的某些属性 class STR_BINDING { public: wchar_t* wszBuffer{}; //存储数据值,null值存储为NULL bool isNULL{}; //是否为null,为true表示为null,反之不为null /* sql 数据类型编码 #define SQL_UNKNOWN_TYPE 0 #define SQL_CHAR 1 #define SQL_NUMERIC 2 #define SQL_DECIMAL 3 #define SQL_INTEGER 4 #define SQL_SMALLINT 5 #define SQL_FLOAT 6 #define SQL_REAL 7 #define SQL_DOUBLE 8 #if (ODBCVER >= 0x0300) #define SQL_DATETIME 9 #endif #define SQL_VARCHAR 12 #define SQL_WVARCHAR -9 //参阅:https://social.msdn.microsoft.com/Forums/en-US/65adf059-4809-44bb-b0c4-e224260f3b2e/sqlcolattribute-with-sqldescconcisetype-returns-9 */ SQLLEN valueTYPE{}; //值的类型 SQLLEN StrLen_or_IndPtr{}; //供SQLFetch、 SQLFetchScroll、 SQLBulkOperations和 SQLSetPos 等函数返回值的长度或者其他标志值,如:可供返回的数据的长度、SQL_NO_TOTAL、SQL_NULL_DATA size_t dataWidh{}; //存储数据值的宽度,他应当是数据库引擎返回来的值,倘若数据库引擎不返回,那么设置为wszBuffer的长度 /*析构函数*/ virtual~STR_BINDING(); /*默认构造函数*/ STR_BINDING(); /*构造函数,将值初始化为给定值,同时标识该值不为NULL,值类型设定为SQL_VARCHAR*/ STR_BINDING(const std::wstring &str); /*复制构造函数*/ STR_BINDING(const STR_BINDING& sb); /*重载赋值运算符*/ STR_BINDING& operator=(const STR_BINDING& sb); /*重载赋值运算符*/ STR_BINDING& operator=(const std::wstring& str); /*设置值*/ void setValue(const std::wstring &value, bool isNull, SQLLEN type); /*清理,释放内存*/ void clear(); /*初始化,改变变量的值为初始情况,因为对于null值,odbc不会覆盖写入的*/ void init(); }; #endif // !LINK_SQL_SERVER_H_
Link_SQL_Server.cpp

#include "Link_SQL_Server.h" Link_SQL_Server::~Link_SQL_Server() { // 释放ODBC句柄并退出 if (this->hStmt) { SQLFreeHandle(SQL_HANDLE_STMT, hStmt); } if (this->hDbc) { SQLDisconnect(hDbc); SQLFreeHandle(SQL_HANDLE_DBC, hDbc); } if (this->hEnv) { SQLFreeHandle(SQL_HANDLE_ENV, hEnv); } } bool Link_SQL_Server::CreateDataSource(std::wstring dsn, std::wstring database, std::wstring server, std::wstring description, std::wstring trusted_connection) { this->DSN = dsn; this->Database = database; this->Server = server; this->Description = description; trusted_connection._Equal(L"yes") ? this->Trusted_Connection = trusted_connection : this->Trusted_Connection = L"no"; this->LinkStatement = L"DSN=" + this->DSN + L";Database=" + this->Database + L";Server=" + this->Server + L";Description=" + this->Description + L";Trusted_Connection=" + this->Trusted_Connection + L";"; unsigned len = this->LinkStatement.size(); wchar_t* LinkStatement_ = new wchar_t[static_cast<long>(len + 1)]{}; wcsncpy_s(LinkStatement_, (len + 1), this->LinkStatement.c_str(), len); for (unsigned i{}; i < len + 1; i++) { if (LinkStatement_[i] == L';') { LinkStatement_[i] = L'\0'; } } /* * SQLConfigDataSource说明: hwndParent:指定为 ODBC 驱动程序管理器或特定 ODBC 驱动程序创建的任何对话框的所有者的窗口,以便从用户处获取有关新数据源的其他信息。 如果 lpszAttributes 参数不提供足够的信息,则会显示一个对话框。 hwndParent 参数可能为 NULL fRequest: ODBC_ADD_DSN: 增加一个新的用户数据源 ODBC_CONHG_DSN: 配置(修改)一个已经存在的数据源 ODBC_REMOVE_DSN: 删除一个已经存在的数据源 ODBC_ADD_SYS_DSN: 增加一个新的系统数据源 ODBC_CONFIG_SYS_DSN: 更改一个已经存在的系统数据源 ODBC_REMOVE_SYS_DSN: 删除一个已经存在的系统数据源 lpszDriver:是数据库引擎名称,也就是驱动程序名称;在ODBC管理程序里面可以看到 lpszAttributes:格式为“keyname=value”的属性列表。 这些字符串由 null 终止符分隔,列表末尾有两个连续 null 终止符。 这些属性主要是默认的特定于驱动程序的条目,这些条目进入新数据源的注册表中。 */ if (!SQLConfigDataSource(nullptr, ODBC_ADD_DSN, this->Driver.c_str(), LinkStatement_)) { //SQLConfigDataSource函数是可以多次调用的,不会因为DSN已存在就报错的。 //throw L"创建用户数据源失败!"; delete[]LinkStatement_; return false; } else { delete[]LinkStatement_; return true; } } bool Link_SQL_Server::TRYODBC(SQLHENV handle, SQLSMALLINT handleType, RETCODE value) { RETCODE rc = value; if (rc != SQL_SUCCESS) { this->HandleDiagnosticRecord(handle, handleType, rc); return false; } if (rc == SQL_ERROR) { //fwprintf(stderr, L"Error in %d\n", rc); std::wcerr << L"错误代码: " << value << std::endl; return false; } return true; } void Link_SQL_Server::get_errInfos(std::wstring* err_infos, unsigned short size) { //需要确保错误信息的顺序 /*size > Link_SQL_Server::err_array_len ? size = Link_SQL_Server::err_array_len : size = size; if (this->err_infos[err_array_len - 1].size() == 0) { for (unsigned short i{}; i < size; i++) { *(err_infos + i) = this->err_infos[i]; } } else { unsigned i{}; (this->errInfo_index - size) >= 0 ? i = this->errInfo_index - size : i = Link_SQL_Server::err_array_len - size + this->errInfo_index; for (unsigned j{};j < size; i++, j++) { if (i == Link_SQL_Server::err_array_len) { i = 0; } *(err_infos + j) = this->err_infos[i]; } }*/ //不采用上面的字符数组的形式了,将所有的错误信息拼接为长字符串返回去 size > Link_SQL_Server::err_array_len ? size = Link_SQL_Server::err_array_len : size = size; *err_infos = L""; if (this->err_infos[err_array_len - 1].size() == 0) { for (unsigned short i{}; i < size; i++) { *err_infos += this->err_infos[i]; } } else { unsigned i{}; (this->errInfo_index - size) >= 0 ? i = this->errInfo_index - size : i = Link_SQL_Server::err_array_len - size + this->errInfo_index; for (unsigned j{}; j < size; i++, j++) { if (i == Link_SQL_Server::err_array_len) { i = 0; } *err_infos += this->err_infos[i]; } } } void Link_SQL_Server::get_errInfos() { if (this->err_infos[this->errInfo_index].size() > 0) { //已经循环过了 for (unsigned i = this->errInfo_index; i < Link_SQL_Server::err_array_len; i++) { if (i == Link_SQL_Server::err_array_len) { i = 0; } std::wcout << this->err_infos[i] << std::endl; } } else { //还没有循环 for (unsigned i{}; i < this->errInfo_index; i++) { std::wcout << this->err_infos[i] << std::endl; } } } void Link_SQL_Server::set_errInfo(const std::wstring& errInfo) { this->errInfo_index >= 5 ? this->errInfo_index = 0 : this->errInfo_index = this->errInfo_index; this->err_infos[this->errInfo_index] = errInfo; this->errInfo_index++; } std::wstring Link_SQL_Server::HandleDiagnosticRecord(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode) { SQLSMALLINT iRec = 0; //指示函数SQLGetDiagRec查找信息的状态记录 SQLINTEGER iError; //指向缓冲区的指针,该缓冲区返回特定于数据源的本机错误代码。 WCHAR wszMessage[1024]{}; //报错诊断记录 RecNumber (和终止 NULL) 的 SQLSTATE 代码。 前两个字符指示 类;接下来的三个指示子类。 此信息包含在诊断SQL_DIAG_SQLSTATE字段。 有关详细信息,请参阅 https://docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/sqlstates?view=sql-server-ver16 WCHAR wszState[SQL_SQLSTATE_SIZE + 1]{}; //该缓冲区存储特定于数据源的本机错误代码。 此信息包含在诊断SQL_DIAG_NATIVE中。 std::wstring err_info; //SQL_INVALID_HANDLE:函数失败,因为环境句柄、连接句柄或语句句柄无效。 这表明编程错误。 if (RetCode == SQL_INVALID_HANDLE) { switch (hType) { case SQL_HANDLE_DBC: err_info = L":函数失败,因为连接句柄无效!"; break; case SQL_HANDLE_STMT: err_info = L":函数失败,因为语句句柄无效!"; break; case SQL_HANDLE_ENV: err_info = L":函数失败,因为环境句柄无效!"; break; default: err_info = L"函数失败,未知原因:句柄类型:"; err_info += hType; break; } this->set_errInfo(err_info); //fwprintf(stderr, L"Invalid handle!\n"); return err_info; } //SQLGetDiagRec 返回包含错误、警告和状态信息的诊断记录的多个字段的当前值。 与 SQLGetDiagField(每个调用返回一个诊断字段)不同,SQLGetDiagRec 返回诊断记录的几个常用字段,包括 SQLSTATE、本机错误代码和诊断消息文本。 while (SQLGetDiagRec(hType, hHandle, ++iRec, wszState, &iError, wszMessage, (SQLSMALLINT)(sizeof(wszMessage) / sizeof(WCHAR)), (SQLSMALLINT*)NULL) == SQL_SUCCESS) { err_info = L"["; err_info += wszState; err_info += L"] "; err_info += wszMessage; err_info += L" "; err_info += iError; err_info += L'\n'; //https://docs.microsoft.com/zh-cn/sql/odbc/reference/syntax/sqlexecdirect-function?view=sql-server-ver16 //目前已知字符ᙅ、ᙇ会造成控制台不能输出字符。 replace(err_info, 0, err_info.size(), L"ᙅ", L"", true); replace(err_info, 0, err_info.size(), L"ᙇ", L"", true); replace(err_info, 0, err_info.size(), L"ล", L"", true); this->set_errInfo(err_info); } return err_info; } bool Link_SQL_Server::link(const std::wstring& conn_str) { std::wstring err_info; bool ret{}; // 1、分配一个环境 //若要请求环境句柄,应用程序将调用 SQLAllocHandle ,HandleType必须为SQL_HANDLE_ENV;inputhandle 必须为SQL_NULL_HANDLE;OutputHandlePtr参数返回关联句柄的值 //参阅:https://docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/allocating-the-environment-handle?view=sql-server-ver16 if (SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &this->hEnv) == SQL_ERROR) { err_info = L"无法分配环境句柄\n"; this->set_errInfo(err_info); //fwprintf(stderr, L"无法分配环境句柄\n"); //exit(-1); this->is_linkSuccess = false; return false; } // 将其注册为。如果你使用AllocHandle,你必须注册一些东西 //TRYODBC处理潜在的错误 if (!TRYODBC(hEnv, SQL_HANDLE_ENV, SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0))) { return false; } // 分配一个连接 // 若要请求连接句柄,应用程序将使用 SQL_HANDLE_DBC 的HandleType调用SQLAllocHandle 。 将 inputhandle参数设置为调用分配该句柄的SQLAllocHandle时所返回的环境句柄。 驱动程序为连接信息分配内存,并将关联的句柄的值传递回 * OutputHandlePtr中。 应用程序在需要连接句柄的所有后续调用中传递 * OutputHandlePtr 值。 // 参阅:https://docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/allocating-a-connection-handle-odbc?view=sql-server-ver16 //TRYODBC处理潜在的错误 if (!TRYODBC(hEnv, SQL_HANDLE_ENV, SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc))) { this->is_linkSuccess = false; return false; } size_t len = conn_str.size(); std::shared_ptr<wchar_t> pwszConnStr(new wchar_t[len + 1]{}); // wchar_t* pwszConnStr = new wchar_t[len + 1]{}; wcsncpy_s(pwszConnStr.get(), len + 1, conn_str.c_str(), len); // 连接数据源。如果在输入中提供了连接字符串,则使用连接字符串,否则让驱动程序管理器提示输入。 //SQLDriverConnect:https://docs.microsoft.com/zh-cn/sql/odbc/reference/syntax/sqldriverconnect-function?f1url=%3FappId%3DDev16IDEF1%26l%3DZH-CN%26k%3Dk(SQLUCODE%252FSQLDriverConnect)%3Bk(SQLDriverConnect)%3Bk(DevLang-C%252B%252B)%3Bk(TargetOS-Windows)%26rd%3Dtrue&view=sql-server-ver16 if (TRYODBC(hDbc, SQL_HANDLE_DBC, SQLDriverConnect(hDbc, GetDesktopWindow(), //检索桌面窗口的句柄。桌面窗口覆盖整个屏幕。桌面窗口是在其上绘制其他窗口的区域。 pwszConnStr.get(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE) )) { this->is_linkSuccess = false; return false; } //delete[]pwszConnStr; //分配语句句柄 //参阅:https://docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/statement-handles?view=sql-server-ver16 if (!TRYODBC(hDbc, SQL_HANDLE_DBC, SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt))) { this->is_linkSuccess = false; return false; } //提示连接成功 //std::wcout << L"链接成功!\n"; this->is_linkSuccess = true; return true; } void Link_SQL_Server::setpwd() { std::wstring line; //用来保存输入一行的密码的变量 wchar_t ch; while (ch = _getch()) //单个字符的获取 { if (ch == L'\n' || ch == L'\r')break; else if (ch == L'\b') { //当输入退格符时 line = line.substr(0, line.length() - 1); //删除字符串最后一个字符 std::wcout << L"\b" << L" " << L"\b"; // 光标退一个显示为空,在退一格 } else { _putch('*'); line += ch; } } this->PWD = line; } void Link_SQL_Server::setpwd(std::wstring& pwd) { this->PWD = pwd; } void Link_SQL_Server::set_UID_PWD() { std::wcout << L"请输入账号:\n"; std::getline(std::wcin, this->UID); if (this->PWD.size() == 0) { std::wcout << L"请输入密码:\n"; this->setpwd(); } } bool Link_SQL_Server::link() { if (!(this->UID.size() > 0 && this->PWD.size() > 0)) { this->set_UID_PWD(); } this->ConnStr = L"Driver=SQL Server;Server="; this->ConnStr += this->Server; this->ConnStr += L";Database="; this->ConnStr += this->Database; this->ConnStr += L";UID="; if (this->UID.size() == 0) { this->set_errInfo(L"UID为空!"); } this->ConnStr += this->UID; this->ConnStr += L";PWD="; if (this->UID.size() == 0) { this->set_errInfo(L"PWD为空!"); } this->ConnStr += this->PWD; this->ConnStr += L";"; if (this->link(this->ConnStr)) { return true; } else { return false; } } RETCODE Link_SQL_Server::statementExecution(const std::wstring& sql_str, const std::vector<std::wstring>values, const std::vector<unsigned>value_index) { this->value_index = value_index; std::wstring sql_exec_str = sql_str; MY_IN::toUppercase(sql_exec_str,0, sql_exec_str.size()); size_t start{}, end{}; //分别是select的index和from的index start = sql_exec_str.find(L"SELECT"); end = sql_exec_str.find(L"FROM"); if (end == std::wstring::npos || start == std::wstring::npos) { this->set_errInfo(L"没有SELECT或者FROM关键字"); return SQL_ERROR; } std::wstring len_str; for (auto& v : values) { len_str += L"LEN(" + v + L"),"; } len_str = len_str.substr(0, len_str.size() - 1); std::wstring sqlExecStr = L"SELECT " + len_str + sql_exec_str.substr(end, sql_exec_str.size()); std::list<STR_BINDING> dataitem_ret; //存储特定项目的实际长度 this->statementExecution(sqlExecStr); if (this->get_success()) { dataitem_ret = this->getRsults(); } //寻找各列中的最长长度值 std::vector<unsigned> lenValue; //按照列数,初始化lenValue for (unsigned i{}; i < value_index.size(); i++) { lenValue.push_back(0); } unsigned index{}, i{}; //index计数当前是第几个,i作为下标 for(auto&v: dataitem_ret){ unsigned len_num{}; if (v.isNULL) { len_num = 0; } else { len_num = std::stoi(std::wstring(v.wszBuffer)); } i = index % value_index.size(); lenValue[i] = lenValue[i] < len_num ? len_num : lenValue[i]; index++; } //将列index和最长长对应起来 for (int i{}; i < values.size(); i++) { this->index_len.insert(std::unordered_map<int, int>::value_type(value_index[i], lenValue[i])); } this->exc_retCode = this->statementExecution(sql_str); this->index_len.clear(); this->value_index.clear(); return this->exc_retCode; } RETCODE Link_SQL_Server::statementExecution(const std::wstring& sql_str) { //如果语句中存在任何参数, SQLExecDirect将使用参数标记变量的当前值执行可准备对象语句。 SQLExecDirect是提交用于一次性执行的 SQL 语句的最快方法。 std::shared_ptr<SQLWCHAR> sql_input(new SQLWCHAR[sql_str.size() + 1]{}); wcsncpy_s(sql_input.get(), sql_str.size() + 1, sql_str.c_str(), sql_str.size()); this->exc_retCode = SQLExecDirect(hStmt, sql_input.get(), SQL_NTS); this->check_excRsult(); //检查结果并获取结果 /*SQLFreeStmt 停止与特定语句关联的处理、关闭与该语句关联的任何打开的游标、放弃挂起的结果,或者,还可以释放与该语句句柄关联的所有资源。 SQL_CLOSE:表示关闭hStmt所表示的游标*/ if (!TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLFreeStmt(this->hStmt, SQL_CLOSE))) { this->is_success = false; } return this->exc_retCode; } bool Link_SQL_Server::check_excRsult() { switch (this->exc_retCode) { case SQL_SUCCESS_WITH_INFO: { //这里是一些常规的警告信息,应该归类于结果中 this->freedResult(); //清空之前的结果。 std::wstring t_str = this->HandleDiagnosticRecord(this->hStmt, SQL_HANDLE_STMT, this->exc_retCode); STR_BINDING t(t_str); this->sql_retValues.push_back(t); //设置列数的值为1 this->sNumResults = 1; this->is_success = true; return true; } case SQL_SUCCESS: //此处需要调用结果获取函数 if (this->get_excRsult()) { this->is_success = true; return true; } else { this->is_success = true; return true; } case SQL_ERROR: //显示错误/警告信息 HandleDiagnosticRecord(hStmt, SQL_HANDLE_STMT, this->exc_retCode); this->is_success = false; return false; case SQL_INVALID_HANDLE: //显示错误/警告信息 HandleDiagnosticRecord(hStmt, SQL_HANDLE_STMT, this->exc_retCode); this->is_success = false; return false; default: std::wstring err; err = L"意外的返回代码:"; err += std::to_wstring(this->exc_retCode); this->set_errInfo(err); this->is_success = false; return false; } } bool Link_SQL_Server::get_excRsult() { if (!this->TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLNumResultCols(hStmt, &this->sNumResults))) { this->is_success = false; return false; } this->freedResult(); //清空之前的结果。 if (this->sNumResults > 0) { //预分配空间 this->pre_allocation_bind(); //获取结果值 RETCODE RetCode = SQL_SUCCESS; //this->sql_retValues.clear(); //清空之前的结果。clear this->freedResult(); while (true) { RetCode = SQLFetch(hStmt); //创建结果集时,游标将定位在结果集的开始位置。 SQLFetch 提取下一行集。 它等效于调用 SQLFetchScroll , if (RetCode == SQL_ERROR || RetCode == SQL_SUCCESS_WITH_INFO) { TRYODBC(hStmt, SQL_HANDLE_STMT, RetCode); } if (RetCode == SQL_SUCCESS || RetCode == SQL_SUCCESS_WITH_INFO) { for (auto& value : this->sql_values) { if (value.StrLen_or_IndPtr == SQL_NULL_DATA) { value.isNULL = true; //value.setValue(L"NULL", true, value.valueTYPE); } else { value.isNULL = false; } this->sql_retValues.push_back(value); value.init(); } } else { break; } } //this->sql_values.clear(); std::vector<STR_BINDING> t; this->sql_values.swap(t); //交换后,就能被销毁掉 this->is_success = true; return true; } else { SQLLEN cRowCount{}; std::wstring message; //SQLRowCount 返回 受 UPDATE、 INSERT 或 DELETE 语句影响的 行 数; if (!this->TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLRowCount(this->hStmt, &cRowCount))) { this->is_success = false; return false; } if (cRowCount >= 0) { STR_BINDING ret; std::wstring ret_str = L""; ret_str = std::to_wstring(cRowCount); ret_str += L" 行受影响"; ret.setValue(ret_str, false, SQL_VARCHAR); this->sql_retValues.push_back(ret); this->is_success = true; this->sNumResults = 1; //将提示也作为结果输出,所以至少是一列。 return true; } this->is_success = false; return true; } } void Link_SQL_Server::pre_allocation_bind() { unsigned iCol{ 1 }; //列顺序,必须从1开始 SQLLEN valueSize{}, ssType{}; //值的size,值的类型 SQLSMALLINT cchColumnNameLength{}; //列名称长度 //准备sql_values std::vector<STR_BINDING> t; this->sql_values.swap(t); //必须这么干,不然绑定就名存实亡 for (int i{}; i < this->sNumResults; i++) { STR_BINDING temp; this->sql_values.push_back(temp); } for (; iCol <= this->sNumResults; iCol++) { STR_BINDING& temp = this->sql_values.at(iCol - 1); //计算出列的长度 if ((this->index_len.size() > 0) && (MY_IN::isExist(iCol,this->value_index)!= -1)) { //使用实际长度 valueSize = this->index_len[iCol]; } else { if (!TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLColAttribute( this->hStmt, //语句句柄 iCol, //用于检索字段值的记录数。 此参数对应于结果数据的列数,按列顺序(从 1 开始)按顺序排序。 可以按任意顺序描述列。可以在此参数中指定列 0,但除 SQL_DESC_TYPE 和 SQL_DESC_OCTET_LENGTH之外的所有值都将返回未定义的值。 SQL_DESC_DISPLAY_SIZE, //描述符句柄,SQL_DESC_DISPLAY_SIZE:显示列数据所需的最大字符数。值放到最后一个参数中 //SQL_DESC_LENGTH, //一个数值,该值是字符串或二进制数据类型的最大或实际字符长度。 它是固定长度数据类型的最大字符长度,或可变长度数据类型的实际字符长度。 其值始终排除结束字符串的 null 终止字节。 nullptr, //指向缓冲区的指针,如果字段是字符串,则返回 IRD 的 ColumnNumber 行的FieldIdentifier 字段中的值。 否则,将不使用 字段。 //如果 CharacterAttributePtr 为 NULL, 则 StringLengthPtr 仍将返回总字节数 0, //前一个指针的指向的缓冲区长度 nullptr, //[输出]指向缓冲区的指针,在缓冲区中返回的字节总数 &valueSize) //[输出]指向整数缓冲区的指针,用来存储 )) { this->is_success = false; return; } else { //设置值的宽度 bool isExsit{}; for (auto& v : this->value_index) { if (v == iCol) { break; isExsit = true; } } if (isExsit) { this->sql_values.at(iCol - 1).dataWidh = this->index_len[iCol]; } else { this->sql_values.at(iCol - 1).dataWidh = valueSize; } } } //判断值类型 if (!TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLColAttribute( this->hStmt, iCol, SQL_DESC_CONCISE_TYPE, //描述符,该描述符返回简洁的数据类型,简洁是指不那么精确的表示数据类型,比如日期数据类型 nullptr, 0, nullptr, &ssType //[输出]指向整数缓冲区的指针,用来存储 ))) { this->is_success = false; return; } temp.wszBuffer = new wchar_t[valueSize + 1]{}; temp.valueTYPE = ssType; // 将此缓冲区映射到驱动程序的缓冲区。 在 Fetch 时,驱动程序将填写此数据。 请注意,大小是字节数(对于 Unicode)。 所有使用 SQLPOINTER 的 ODBC 函数都使用字节数; 所有只接受字符串的函数都使用字符数。 //SQLBindCol:这里使用函数的意思是,假如你有个数据表,有4列,10行,那么他就需要4个缓冲区来存储每一行;而不是要40个缓冲区。所以后面要调用的是SQLFetch 函数而不是其他函数,循环的方式读取每一行 if (!TRYODBC(this->hStmt, SQL_HANDLE_STMT, SQLBindCol(this->hStmt, iCol, //ColumnNumber:送要绑定的结果集列的编号。 列按递增的列顺序编号,从0开始,其中列0是书签列。 如果未使用书签(即,SQL_ATTR_USE_BOOKMARKS 语句特性设置为 SQL_UB_OFF,则列号从1开始。 SQL_C_WCHAR, //TargetType:缓冲区的 C 数据类型的标识符,参考:https://docs.microsoft.com/zh-cn/sql/odbc/reference/appendixes/c-data-types?view=sql-server-ver16 temp.wszBuffer, //TargetValuePtr:[延迟的输入/输出]指向要绑定到列的数据缓冲区的指针。 //如果 TargetValuePtr 为 null 指针,则驱动程序将为列解除数据缓冲区的绑定。 (valueSize + 1) * sizeof(wchar_t), //TargetValuePtr 缓冲区的长度(以字节为单位) &temp.StrLen_or_IndPtr //[延迟的输入/输出]指向要绑定到列的长度/指示器缓冲区的指针。SQLFetch 和 SQLFetchScroll 返回此缓冲区中的值。这个值表示返回的值是null还是常规值的长度 ))) { this->is_success = false; return; } } this->is_success = true; } bool Link_SQL_Server::freedResult() { this->sql_retValues.clear(); /*size_t len = this->sql_retValues.size(); for (size_t i{}; i < len; i++) { this->sql_retValues.pop_back(); }*/ return true; } unsigned Link_SQL_Server::get_Col_num()const { return this->sNumResults; } bool Link_SQL_Server::get_isLink() { return this->is_linkSuccess; } STR_BINDING::STR_BINDING() { this->wszBuffer = new wchar_t[1]{}; this->isNULL = false; this->StrLen_or_IndPtr = 0; this->valueTYPE = 0; } STR_BINDING::~STR_BINDING() { if (this->wszBuffer != nullptr) { delete[] this->wszBuffer; this->wszBuffer = nullptr; } } STR_BINDING::STR_BINDING(const std::wstring& str) { size_t len = str.size(); this->wszBuffer = new wchar_t[len + 1]{}; wcsncpy_s(this->wszBuffer, len + 1, str.c_str(), len); this->isNULL = false; this->valueTYPE = SQL_VARCHAR; } STR_BINDING::STR_BINDING(const STR_BINDING& sb) { size_t len = wcslen(sb.wszBuffer); this->wszBuffer = new wchar_t[len + 1]{}; wcsncpy_s(this->wszBuffer, len + 1, sb.wszBuffer, len); this->isNULL = sb.isNULL; this->valueTYPE = sb.valueTYPE; } STR_BINDING& STR_BINDING::operator=(const STR_BINDING& sb) { this->setValue(sb.wszBuffer, sb.isNULL, sb.valueTYPE); return *this; } STR_BINDING& STR_BINDING::operator=(const std::wstring& str) { this->setValue(str, false, SQL_VARCHAR); return *this; } void STR_BINDING::setValue(const std::wstring& value, bool isNull, SQLLEN type) { this->~STR_BINDING(); size_t len = value.size(); this->wszBuffer = new wchar_t[len + 1]{}; wcsncpy_s(this->wszBuffer, len + 1, value.c_str(), len); this->isNULL = isNull; this->valueTYPE = type; } void STR_BINDING::clear() { this->~STR_BINDING(); } void STR_BINDING::init() { this->wszBuffer[0] = L'\0'; this->isNULL = true; }
一个使用示例
#include <iostream> #include <iomanip> //输出控制 #include <list> #include "Link_SQL_Server.h" void show(std::list<STR_BINDING>& data, unsigned colNum); int main() { std::locale::global(std::locale("")); //设置宽字符显示本地环境 std::list<STR_BINDING> ret; //存储sql语句返回的结果 Link_SQL_Server a; //创建数据源,重复创建不会报错 if (!a.CreateDataSource(L"my_sqlServer")) { std::wcout << L"创建数据源失败" << std::endl; } //连接数据库 //if (!a.link()) { if (!a.link(L"Driver=SQL Server;Server=127.0.0.1;Database=master;UID=sa;PWD=*****@***;")) { a.get_errInfos(); std::wcout << L"err" << std::endl; } else { //连接成功 std::wcout << L"ok" << std::endl; a.statementExecution(L"use test_01"); //执行sql语句 if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } //查询 a.statementExecution(L"select * from student"); if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } //插入 a.statementExecution(L"insert into student(student.id,student.name,student.age) values(4, N'王大麻子',22);"); if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } //查询 a.statementExecution(L"select * from student"); if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } //删除 a.statementExecution(L"delete from student where id=4;"); if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } //查询 a.statementExecution(L"select * from student"); if (a.get_success()) { ret = a.getRsults(); show(ret, a.get_Col_num()); } else { a.get_errInfos(); } } return 0; } void show(std::list<STR_BINDING>& data, unsigned colNum) { unsigned index{}; for (auto &value : data) { index++; std::wcout << std::setw(16) << value.wszBuffer; if (index % colNum == 0) { std::wcout << std::endl; } } }
盲猜还有许多bug在里面,在后续的使用过程中遇到了就改一改吧。
2022年8月3日
重载了statementExecution函数,应对varchar(max)的情况。当列为 varchar(max)、nvarchar(max)等情况下,ODBC会为单个列申请超过1G的内存空间,然而实际上并不需要用到这么多的内存。
使用示例:
std::vector<std::wstring> values{ L"[edit_log]" }; std::vector<unsigned> values_index{ 1 }; std::list<STR_BINDING>ret; //存储查询的结果 std::wstring sql_exec_str = L"select [edit_log],[remark] FROM [canyin].[dbo].[bill_Info]"; statementExecution(sql_exec_str, values, values_index); if (!get_success()) { std::wstring err_str; eb.sql.get_errInfos(&err_str); QMessageBox::about(this, "error:查询原备注失败", QString::fromStdWString(err_str)); //QString::fromStdWString:将wstring转为QString fail_num++; continue; } else { ret = eb.sql.getRsults(); }
分类:
C++
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求