狂自私

导航

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_
View Code

 

 

 

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;
}
View Code

 

 

 

一个使用示例

#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();
}

 

posted on 2022-06-04 13:31  狂自私  阅读(1838)  评论(3编辑  收藏  举报