注册
达梦与其他数据库在odbc的SQLBindParameter表现上的对比
培训园地/ 文章详情 /

达梦与其他数据库在odbc的SQLBindParameter表现上的对比

2023/09/25 1448 0 0

背景

在把数据从oracle迁移到达梦后,原本正常的应用程序报了字符串截断的错误:
image.png

分析

经分析后发现问题在于,数据库中有一个字段类型为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报错,还是建议程序员在绑定参数前,确保字符串长度不超长,或者提前截取合法的长度,再进行参数绑定。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服