在把数据从oracle迁移到达梦后,原本正常的应用程序报了字符串截断的错误:
经分析后发现问题在于,数据库中有一个字段类型为char(1),向其中插入了一个定义如SQLCHAR name1[] = "abcd"的数据,显然这个数据过长了。
但有趣的是,通过SQLBindParameter函数进行参数绑定时有一个BufferLength参数可以限制传入字符的长度,即在传入数据库前就提前把字符串截断了,可能正是因为这个参数在oracle中有效,达梦中无效才会报错。
函数的原型
SQLRETURN SQLBindParameter(
SQLHSTMT StatementHandle,
SQLUSMALLINT ParameterNumber,
SQLSMALLINT InputOutputType,
SQLSMALLINT ValueType,
SQLSMALLINT ParameterType,
SQLULEN ColumnSize,
SQLSMALLINT DecimalDigits,
SQLPOINTER ParameterValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr
);
编写了以下程序odbc_SQLBindParameter_char1.c,只修改连接串,分别在dm8,oracle11g,mysql,SQL server2019上进行测试
#include <stdio.h>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
//检测返回代码是否为成功标志,当为成功标志返回 TRUE,否则返回 FALSE
#define RC_SUCCESSFUL(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO)
//检测返回代码是否为失败标志,当为失败标志返回 TRUE,否则返回 FALSE
#define RC_NOTSUCCESSFUL(rc) (!(RC_SUCCESSFUL(rc)))
HENV henv; //环境句柄
HDBC hdbc; //连接句柄
HSTMT hsmt; //语句句柄
SQLRETURN sret; //返回代码
SQLINTEGER AUTOCOMMIT_MODE;
int main(void)
{
//申请一个环境句柄
SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
//设置环境句柄的 ODBC 版本
SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER);
//申请一个连接句柄
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
sret = SQLConnect(hdbc, (SQLCHAR *)"mssql", SQL_NTS, (SQLCHAR *)"sa", SQL_NTS, (SQLCHAR *)"Sqlserver123", SQL_NTS);
//收集连接错误信息
if (RC_NOTSUCCESSFUL(sret)) {
//连接数据源失败!
printf("连接数据源失败");
SQLCHAR state[6];
SQLINTEGER error_code;
SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
SQLSMALLINT message_length;
SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, state, &error_code, message, SQL_MAX_MESSAGE_LENGTH, &message_length);
printf("Failed to connect to database: %s\n", message);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
//设置连接句柄属性,关闭自动提交功能
SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF,SQL_IS_INTEGER);
//取得连接句柄属性,取得提交的模式
SQLGetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)&AUTOCOMMIT_MODE, sizeof(SQLINTEGER), NULL);
//申请一个语句句柄
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hsmt);
// 绑定查询参数
SQLINTEGER id1 = 1;
SQLCHAR name1[] = "abcd";
SQLBindParameter(hsmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &id1, 0, NULL);
SQLBindParameter(hsmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 1, 0, name1, 1, NULL);
// 执行问题SQL语句
SQLExecDirect(hsmt, "DROP TABLE IF EXISTS TCHAR;", SQL_NTS);
SQLExecDirect(hsmt, "CREATE TABLE TCHAR(ID INT,NAME CHAR(1))", SQL_NTS);
SQLExecDirect(hsmt, "INSERT INTO TCHAR VALUES (?, ?)", SQL_NTS);
//收集SQL错误信息
SQLCHAR sqlstate[6];
SQLINTEGER nativeerror;
SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
SQLSMALLINT msglen;
SQLRETURN ret;
SQLSMALLINT i = 1;
while ((ret = SQLGetDiagRec(SQL_HANDLE_STMT, hsmt, i++, sqlstate, &nativeerror, message, sizeof(message), &msglen)) != SQL_NO_DATA) {
printf("SQLSTATE: %s\n", sqlstate);
printf("Native Error: %d\n", nativeerror);
printf("Message: %.*s\n", msglen, message);
}
sret=SQLExecDirect(hsmt, "SELECT * FROM TCHAR;", SQL_NTS);
SQLINTEGER id;
SQLCHAR name[5];
SQLBindCol(hsmt, 1, SQL_C_LONG, &id, 0, NULL);
SQLBindCol(hsmt, 2, SQL_C_CHAR, name, sizeof(name), NULL);
while (SQLFetch(hsmt) == SQL_SUCCESS)
{
printf("ID: %d, NAME: %s\n", id, name);
}
SQLExecDirect(hsmt, "DROP TABLE TCHAR;", SQL_NTS);
//关闭游标,终止语句执行
SQLCloseCursor(hsmt);
//释放语句句柄
SQLFreeHandle(SQL_HANDLE_STMT, hsmt);
//提交连接上的事务
SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
//断开与数据源之间的连接
SQLDisconnect(hdbc);
//释放连接句柄
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
//释放环境句柄
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
安装unixODBC,数据源配置如下
odbc.ini
[dm8]
Description = DM ODBC DSN
Driver = DM8 ODBC DRIVER
SERVER = localhost
UID = SYSDBA
PWD = SYSDBA
TCP_PORT = 5236
[mysql]
Driver = MySQL
Description = MyODBC 5 Driver
SERVER = localhost
PORT = 3306
USER = root
Password = Jyt_2023
Database = test
[oracle]
Driver = ORACLE
server = localhost
port = 1521
Database =ORCLCDB
UserID = c##u1
password = 123456
[mssql]
Description = sqlserver connection
Driver = ODBC Driver 17 for SQL Server
TDS_Version = 8.0
Server = localhost
Port = 1433
User = sa
Password = Sqlserver123
Database = test
odbcinst.ini
[MySQL]
Description=ODBC for MySQL
Driver=/usr/lib/libmyodbc8a.so
Setup=/usr/lib/libodbcmyS.so
Driver64=/usr/lib64/libmyodbc8a.so
Setup64=/usr/lib64/libodbcmyS.so
FileUsage=1
[DM8 ODBC DRIVER]
Description=ODBC DRIVER FOR DM8
DRIVER=/dm8/bin/libdodbc.so
[ORACLE]
Description=Easysoft ODBC Oracle Driver
Driver=/opt/oracle/product/19c/dbhome_1/lib/libsqora.so.19.1
[ODBC Driver 17 for SQL Server]
Description=Microsoft ODBC Driver 17 for SQL Server
Driver=/opt/microsoft/msodbcsql17/lib64/libmsodbcsql-17.6.so.1.1
UsageCount=1
编译、运行
gcc odbc_SQLBindParameter_char1.c -lodbc
./a.out
oracle、mysql的输出:
ID: 1, NAME: a
达梦的输出:
SQLSTATE: 22001
Native Error: -70005
Message: 字符串截断
SQL server的输出:
SQLSTATE: 22001
Native Error: 0
Message: [Microsoft][ODBC Driver 17 for SQL Server]String data, right truncation
关于语句
SQLBindParameter(hsmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 1, 0, name1, 1, NULL);
dm和SQL server表现相同,绑定参数时设置bufferlength无效,报错字符串截断,不能插入数据
oracle和mysql设置有效,成功插入了第一个字符
由于这本身也是一个不合理的用法,尽管oracle和mysql不报错,dm和SQL server报错,还是建议程序员在绑定参数前,确保字符串长度不超长,或者提前截取合法的长度,再进行参数绑定。
文章
阅读量
获赞