如何避免将SQL_BINARY数组数据作为parameter passing时终止? (ODBC驱动程序)

我刚刚学会了Windows ODBC驱动程序API需要一个SQL_BINARY数据数组作为input参数以零字节终止的SQL_BINARY 。 即使我没有在文档中find这样的声明,我通过使用此代码执行存储过程,发现了这一点:

最小例子

 // 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); 

它导致SQLExecDirectSQL_NULL_DATA失败。 在使用SQLGetDiagRec查询诊断logging时,我收到了以下logging:

SQL状态= 22001,错误消息:“[Microsoft] [ODBC SQL Server驱动程序] string或二进制数据将被截断

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

 // 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参数),仍然search零字节?

就我的理解而言,在数组长度不是由长度指示variables确定,而是以零值终止数组的情况下使用零终止。 这通常是用string来完成的。 对于二进制数据,这对我来说没有什么意义,因为在达到实际结束(由长度指示符确定)之前,数组中可能有零字节。 我可能会遇到这个问题,所以如果有一些方法可以避免零终止字节数组会很好

完整的例子

正如在这里的意见所要求的是unit testing的完整代码转储:

 #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 

二进制数据必须是空终止的(如果这是真的,则不能插入任何包含0值的数据,如{ 100, 0, 100, 0, 100 } )。

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

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