C语言 如何避免在将数组数据作为参数传递时SQL_BINARY以零终止数组数据?(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);

它导致SQLExecDirectSQL_NULL_DATA而失败。在使用查询诊断记录时,我收到了SQLGetDiagRec记录:

SQL

状态 = 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: %hdn", 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: %hdn", 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的值必须是缓冲区中保存的数据的长度。请注意,这不能与实际缓冲区大小相同(但必须为 <= 缓冲区大小)。来自 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;
}

最新更新