c - 将 SQL_BINARY 数组数据作为参数传递时如何避免零终止? (ODBC 驱动程序)

标签 c sql-server windows odbc

我刚刚了解到 Windows ODBC 驱动程序 API 需要一组 SQL_BINARY 数据作为输入参数,以零字节结束。尽管我没有在文档中找到这样的语句,但我通过使用以下代码执行存储过程发现了这一点:

最小示例

// Parameter binding
BYTE data[10] = { 15, 3, 54, 144, 34, 211, 200, 147, 15, 74 };
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 10, 0, data, 0, NULL);

// Procedure execution
SQLRETURN res = SQLExecDirect(hstmt, (SQLCHAR*)"{call dbo.Update_Data(?)}", SQL_NTS);

它导致 SQLExecDirect 失败并返回 SQL_NULL_DATA。使用 SQLGetDiagRec 查询诊断记录后,我收到了记录:

SQL State = 22001, Error Msg: "[Microsoft][ODBC SQL Server Driver] String or binary data would be truncated"

虽然这通常表示插入或更新到列中的数据大于列本身,但这里不是这种情况。在尝试了 4 个小时的不同参数和语句后,我终于找到了解决方案,就像在最后一个位置用零终止字节数组一样简单:

// Parameter binding
BYTE data[11] = { 15, 3, 54, 144, 34, 211, 200, 147, 15, 74, 0 }; // <- 0 termination here
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 10, 0, data, 0, NULL);

// Procedure execution
SQLRETURN res = SQLExecDirect(hstmt, (SQLCHAR*)"{call dbo.Update_Data(?)}", SQL_NTS);

现在我不明白为什么会这样?函数 SQLBindParameter 需要给定数据的长度(10 作为 cbColDef 或 ColumnSize 参数)并且仍然搜索零字节?

根据我的理解,零终止用于数组的长度不是由长度指示变量决定,而是数组以零值终止的情况。这通常是用字符串完成的。对于二进制数据,这对我来说没有多大意义,因为在到达实际结尾(由长度指示器确定)之前,数组中可能有预期的零字节。我可能会遇到这个问题,所以如果有一些方法可以避免零终止字节数组,那就太好了?

完整示例

按照评论中的要求,这里是单元测试的完整代码转储:

#include <windows.h>
#include <sql.h>
#include <sqlext.h>

// Settings
#define SIP "127.0.0.1"
#define SPort 1433
#define SUID "User"
#define SPW "PW"
#define SDB "world"

// Global ODBC mem
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;

// Logs Diagnostic records
void ProcessLogs(SQLSMALLINT plm_handle_type, SQLHANDLE &plm_handle);

// The query being tested
void TestQuery()
{
    int col = 0;
    SQLRETURN res = SQL_NTS;

    // Params
    ULONGLONG id = 44;
    BYTE data[10] = { 15, 3, 54, 144, 34, 211, 200, 147, 15, 74 };

    SQLBindParameter(hstmt, ++col, SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, &id, 0, NULL);
    SQLBindParameter(hstmt, ++col, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 10, 0, data, 0, NULL);

    // Execution
    res = SQLExecDirect(hstmt, (UCHAR*)"{call dbo.Update_Store_Data(?,?)}", SQL_NTS);
    if (res != SQL_SUCCESS && res != SQL_SUCCESS_WITH_INFO)
    {
        printf("Error during query execution: %hd\n", res);
        ProcessLogs(SQL_HANDLE_STMT, hstmt);
    }
}

// ODBC Driver initialization
bool ODBCInit()
{
    // Init ODBC Handles
    RETCODE res;
    res = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
    res = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
    res = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

    // Connection string
    char connStr[512];
    sprintf_s(connStr
        , sizeof(connStr)
        , "DRIVER={SQL Server};SERVER=%s;ADDRESS=%s,%d;NETWORK=DBMSSOCN;UID=%s;PWD=%s;DATABASE=%s"
        , SIP
        , SIP
        , SPort
        , SUID
        , SPW
        , SDB);

    // Connection
    char outStr[512];
    SQLSMALLINT pcb;
    res = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr, strlen(connStr), (SQLCHAR*)outStr, ARRAYSIZE(outStr), &pcb, SQL_DRIVER_NOPROMPT);
    if (res != SQL_SUCCESS && res != SQL_SUCCESS_WITH_INFO)
    {
        printf("Error during driver connection: %hd\n", res);
        return false;
    }

    // Query handle
    res = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);

    return true;
}

// ODBC Driver handles cleanup
void ODBCClean()
{
    if (hstmt != SQL_NULL_HSTMT)
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    if (hstmt != SQL_NULL_HDBC)
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    if (hstmt != SQL_NULL_HENV)
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
}

int main(int argc, _TCHAR* argv[])
{
    if (ODBCInit())
        TestQuery();
    ODBCClean();

    return 0;
}

SQL表定义:

CREATE TABLE dbo.Store
(
    UniqueNumber BIGINT IDENTITY(1,1) NOT NULL,
    ItemID BIGINT NOT NULL,
    AccountUniqueNumber BIGINT NOT NULL,
    StorageType INT NOT NULL,
    Count INT NOT NULL,
    Data BINARY(10) NOT NULL
)

调用过程:

CREATE PROCEDURE dbo.Update_Store_Data
    @ID     BIGINT,
    @Data   BINARY(10)
AS
BEGIN
    UPDATE dbo.Store
    SET Data = @Data
    WHERE UniqueNumber = @ID
END

最佳答案

二进制数据必须以 null 结尾是不正确的(如果那是正确的,则不能插入任何包含 0 值的数据,例如 { 100, 0, 100, 0, 100 } }).

  1. 您需要为缓冲区长度(缓冲区大小)设置正确的值。
  2. 您需要正确设置和初始化StrLen_or_IndPtr 参数。对于二进制缓冲区,StrLen_or_IndPtr 的值必须是缓冲区中保存的数据的长度。请注意,这不能与实际缓冲区大小相同(但必须 <= buffersize)。来自 SQLBindParameter 的文档:

The StrLen_or_IndPtr argument points to a buffer that, when SQLExecute or SQLExecDirect is called, contains one of the following [..]:

  • The length of the parameter value stored in *ParameterValuePtr. This is ignored except for character or binary C data.

请参阅下面的最小编译示例:

#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <sqlucode.h>

void printErr(SQLHANDLE handle, SQLSMALLINT handleType)
{
    SQLSMALLINT recNr = 1;
    SQLRETURN ret = SQL_SUCCESS;
    while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
    {
        SQLWCHAR errMsg[SQL_MAX_MESSAGE_LENGTH + 1];
        SQLWCHAR sqlState[5 + 1];
        errMsg[0] = 0;
        SQLINTEGER nativeError;
        SQLSMALLINT cb = 0;
        ret = SQLGetDiagRec(handleType, handle, recNr, sqlState, &nativeError, errMsg, SQL_MAX_MESSAGE_LENGTH + 1, &cb);
        if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
        {
            std::wcerr << L"ERROR; native: " << nativeError << L"; state: " << sqlState << L"; msg: " << errMsg << std::endl;
        }
        ++recNr;
    }
}


int _tmain(int argc, _TCHAR* argv[])
{
    // connect to db
    SQLRETURN   nResult = 0;
    SQLHANDLE   handleEnv = 0;

    nResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, (SQLHANDLE*)&handleEnv);
    nResult = SQLSetEnvAttr(handleEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3_80, SQL_IS_INTEGER);

    SQLHANDLE   handleDBC = 0;
    nResult = SQLAllocHandle(SQL_HANDLE_DBC, handleEnv, (SQLHANDLE*)&handleDBC);

    SQLWCHAR     strConnect[256] = L"Driver={SQL Server Native Client 11.0};Server=.\\TEST;Database=Foobar;Uid=Secret;Pwd=Secret";
    SQLWCHAR     strConnectOut[1024] = { 0 };
    SQLSMALLINT nNumOut = 0;
    nResult = SQLDriverConnect(handleDBC, NULL, (SQLWCHAR*)strConnect, SQL_NTS, (SQLWCHAR*)strConnectOut, sizeof(strConnectOut), &nNumOut, SQL_DRIVER_NOPROMPT);
    if (!SQL_SUCCEEDED(nResult))
        printErr(handleDBC, SQL_HANDLE_DBC);

    // Allocate a statement
    SQLHSTMT    handleStatement = SQL_NULL_HSTMT;
    nResult = SQLAllocHandle(SQL_HANDLE_STMT, handleDBC, (SQLHANDLE*)&handleStatement);
    if (!SQL_SUCCEEDED(nResult))
        printErr(handleDBC, SQL_HANDLE_DBC);

    int col = 0;
    SQLRETURN res = SQL_NTS;

    // Params
    SQLBIGINT id = 2;
    SQLCHAR data[10] = { 15, 3, 54, 144, 34, 211, 200, 147, 15, 74 };
    SQLLEN cbId = 0;
    SQLLEN cbData = 10;

    res = SQLBindParameter(handleStatement, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, &id, sizeof(id), &cbId);
    if (!SQL_SUCCEEDED(res))
        printErr(handleStatement, SQL_HANDLE_STMT);

    res = SQLBindParameter(handleStatement, 2, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, 10, 0, data, sizeof(data), &cbData);
    if (!SQL_SUCCEEDED(res))
        printErr(handleStatement, SQL_HANDLE_STMT);

    // Execution
    res = SQLExecDirect(handleStatement, (SQLWCHAR*)L"{call dbo.Update_Store_Data(?,?)}", SQL_NTS);
    if (!SQL_SUCCEEDED(res))
    {
        printErr(handleStatement, SQL_HANDLE_STMT);
    }

    return 0;
}

关于c - 将 SQL_BINARY 数组数据作为参数传递时如何避免零终止? (ODBC 驱动程序),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43968948/

相关文章:

c - 指向另一个结构体的结构体指针

php - 显示特定用户的详细信息

sql-server - 时区和 DST 的 MS SQL Server 故事(我们需要重新发明轮子吗?)

c++ - 获取优先于提升进程的互斥量

c - 在 C 中,在结束前包含 NULL 的内存块上调用 free() 是否安全?

c - 在 GCC (c99) 中获取基本 trie 结构的 "node has no member"错误

c - MATLAB 生成的 C 代码编译错误

sql - 创建 sql 图

windows - 连续几个int3

java - Android 模拟器失败