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

时间:2017-05-14 21:25:45

标签: 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,错误消息:“[Microsoft] [ODBC SQL Server驱动程序]   字符串或二进制数据将被截断

虽然这通常表示插入或更新到列中的数据大于列本身,但不是这种情况。经过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

1 个答案:

答案 0 :(得分:4)

二进制数据必须以空值终止(如果确实如此,则无法插入任何包含0值的数据,如{ 100, 0, 100, 0, 100 })。

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

    StrLen_or_IndPtr参数指向一个缓冲区,当SQLExecute时   或者调用SQLExecDirect,包含以下[..]之一:

         
        
    • 存储在* ParameterValuePtr中的参数值的长度。除字符或二进制C数据外,这将被忽略。
    •   

    见下面编译的最小例子:

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