场景说明
不同的FreeTDS版本,访问旧版本的SQL Server,可能都需要调用dbsetlversion函数设置TDS版本
当前FreeTDS版本要求1.4.24,如果版本不一致,可能访问不到数据库
测试代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sqlfront.h> /* sqlfront.h always comes first */
#include <sybdb.h> /* sybdb.h is the only other file you need */
#define SQLDBIP "192.168.11.177" //SQL数据库服务器IP
#define SQLDBPORT "1433" //SQL数据库服务器端口
#define SQLDBNAME "car_cp" //SQL数据库服务器数据库名
#define SQLDBUSER "test" //SQL数据库服务器数据库用户名
#define SQLDBPASSWD "cvos" //SQL数据库服务器用户密码
#define SQLDBSERVER "192.168.11.177:1433"
#define DBSQLCMD "select * from dbo.iord"
int msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity,
char *msgtext, char *srvname, char *procname, int line)
{
return 0;
}
int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr,
char *dberrstr, char *oserrstr)
{
//对于错误不处理,仅仅是捕获
return INT_CANCEL;
}
int main(int argc, char *argv[])
{
int i, ch;
LOGINREC *login; //描述客户端的结构体,在连接时被传递到服务器.
DBPROCESS *dbproc; //描述连接的结构体,被dbopen()函数返回
RETCODE erc; //库函数中最普遍的返回类型.
/*************************************************************/
//在开始调用本库函数前常常要先调用dbinit()函数
if (dbinit() == FAIL) {
fprintf(stderr, "%s:%d: dbinit() failed\n", argv[0], __LINE__);
exit(1);
}
//捕获错误,避免直接退出程序
dberrhandle(err_handler);
dbmsghandle(msg_handler);
//dblogin()函数申请 LOGINREC 结构体,此结构体被传递给dbopen()函数,用来创建一个连接。
//虽然基本上不会调用失败,但是检查它!.
if ((login = dblogin()) == NULL) {
fprintf(stderr, "%s:%d: unable to allocate login structure\n", argv[0], __LINE__);
exit(1);
}
dbsetlversion(login, DBVERSION_70);
//LOGINREC结构体不能被直接访问,要通过以下宏设置,下面设置两个必不可少的域
DBSETLUSER(login, SQLDBUSER);
DBSETLPWD(login, SQLDBPASSWD);
/*************************************************************/
//dbopen()与服务器建立一个连接. 传递 LOGINREC 指针和服务器名字
if ((dbproc = dbopen(login, SQLDBSERVER)) == NULL) {
fprintf(stderr, "%s:%d: unable to connect to %s as %s\n",
argv[0], __LINE__,
SQLDBSERVER, SQLDBUSER);
exit(1);
}
// 可以调用dbuser()函数选择我们使用的数据库名,可以省略,省略后使用用户默认数据库.
if (SQLDBNAME && (erc = dbuse(dbproc, SQLDBNAME)) == FAIL) {
fprintf(stderr, "%s:%d: unable to use to database %s\n",
argv[0], __LINE__, SQLDBNAME);
exit(1);
}
/*************************************************************/
dbcmd(dbproc, DBSQLCMD);//将SQL语句填充到命令缓冲区
printf("\n");
if ((erc = dbsqlexec(dbproc)) == FAIL) {
fprintf(stderr, "%s:%d: dbsqlexec() failed\n", argv[0], __LINE__);
exit(1); //等待服务器执行SQL语句,等待时间取决于查询的复杂度。
}
/*************************************************************/
//在调用dbsqlexec()、dbsqlok()、dbrpcsend()返回成功之后调用dbresults()函数
printf("then fetch results:\n");
int count = 0;
while ((erc = dbresults(dbproc)) != NO_MORE_RESULTS) {
struct col { //保存列的所有信息
char *name; //列名字
char *buffer; //存放列数据指针
int type, size, status;
} *columns, *pcol;
int ncols;
int row_code;
if (erc == FAIL) {
fprintf(stderr, "%s:%d: dbresults failed\n",
argv[0], __LINE__);
exit(1);
}
ncols = dbnumcols(dbproc);//返回执行结果的列数目
if ((columns = (col*)calloc(ncols, sizeof(struct col))) == NULL) {
perror(NULL);
exit(1);
}
/* read metadata and bind. */
for (pcol = columns; pcol - columns < ncols; pcol++) {
int c = pcol - columns + 1;
pcol->name = dbcolname(dbproc, c); //返回指定列的列名
pcol->type = dbcoltype(dbproc, c);
pcol->size = dbcollen(dbproc, c);
printf("%*s(%d)", 20, pcol->name, pcol->size);
if ((pcol->buffer = (char *)calloc(1, 20)) == NULL) {
perror(NULL);
exit(1);
}
erc = dbbind(dbproc, c, NTBSTRINGBIND, 20, (BYTE*)pcol->buffer);
if (erc == FAIL) {
fprintf(stderr, "%s:%d: dbbind(%d) failed\n",
argv[0], __LINE__, c);
exit(1);
}
erc = dbnullbind(dbproc, c, &pcol->status); //(5)
if (erc == FAIL) {
fprintf(stderr, "%s:%d: dbnullbind(%d) failed\n",
argv[0], __LINE__, c);
exit(1);
}
}
printf("\n");
/* 打印数据 */
while ((row_code = dbnextrow(dbproc)) != NO_MORE_ROWS) {//读取行数据
switch (row_code) {
case REG_ROW:
for (pcol = columns; pcol - columns < ncols; pcol++)
{
if (pcol->status == -1)
{
const char *buffer = "null";
}
else
{
char *buffer = pcol->buffer;
printf("%*s ", 20, buffer);
}
}
printf("\n"); break;
case BUF_FULL: break;
case FAIL:
fprintf(stderr, "%s:%d: dbresults failed\n",
argv[0], __LINE__);
exit(1); break;
default: // (7)
printf("data for computeid %d ignored\n", row_code);
}
}
/* free metadata and data buffers */
for (pcol = columns; pcol - columns < ncols; pcol++) {
free(pcol->buffer);
}
free(columns);
if (DBCOUNT(dbproc) > -1) /* 得到SQL语句影响的行数 */
{
//fprintf(stderr, "%d rows affected\n", DBCOUNT(dbproc))
}
}
dbclose(dbproc);
dbexit();
return 0;
}
linux简单编译指令
g++ freedtstest.cpp -I./include -L/opt/cmms/3thrdparty/freetds/lib -lct -lsybdb -Wl,-rpath,/opt/cmms/3thrdparty/freetds/lib
Windows下依赖db-lib.lib静态库依赖
freetds-1.4.24\build\src\tds\Debug\tds.lib
freetds-1.4.24\build\src\utils\Debug\tdsutils.lib
freetds-1.4.24\build\src\replacements\Debug\replacements.lib
实际测试结果
Windows环境下运行程序
调用db-lib不需要设置dbsetlversion版本号,SQL Server2008和SQL Server2019测试通过
Linux环境下运行程序
调用db-lib访问SQL Server2008,必须调用
dbsetlversion(login, DBVERSION_70);
依据sybdb.h文件中的定义
/* these two are defined by Microsoft for dbsetlversion() */
#define DBVER42 DBVERSION_42
#define DBVER60 DBVERSION_70 /* our best approximation */
否则提示出错
Adaptive Server connection failed
SQL Server2019设置版本函数调用可选,不影响结果
中文乱码
数据库字符集 :Chinese_PRC_CI_AS
默认情况下,获取到的就是GBK编码的中文
如果设置了DBSETLCHARSET(loginrec, "UTF-8");返回的结果既不是GBK,也不是UTF-8编码,而是乱码的。因此不建议调用
如果设置了DBSETLCHARSET(loginrec, "GBK");反而会导致程序崩溃,错误代码:
if ((size_t)-1 == tds_iconv(tds, char_conv, to_client, &bufp, &bufleft, outbuf, outbytesleft)) {
tdsdump_log(TDS_DBG_NETWORK, "Error: read_and_convert: tds_iconv returned errno %d\n", errno);
if (errno != EILSEQ) {
tdsdump_log(TDS_DBG_NETWORK, "Error: read_and_convert: "
"Gave up converting %u bytes due to error %d.\n",
(unsigned int) bufleft, errno);
tdsdump_dump_buf(TDS_DBG_NETWORK, "Troublesome bytes:", bufp, bufleft);
}
清理缓存
数据库每读一行的时候需要对缓存进行清理,要不然会出现部分乱码的问题,比如上一条内容为:”大家好才是真的好“
而本条内容为:”你吃饭了吗?”实际输出为:
1:大家好才是真的好 (没错误)
2:你吃饭了吗?@#¥#(符号代表乱码)
没请缓存的原因。具体操作用memset(szAid,0,sizeof(szAid)/sizeof(char));
了解下将字符串数组绑定到指定的列,就知道每一次都需要重新置空
Buffer overflow converting characters from client into server's character set
Some character(s) could not be converted into client's character set. Unconverted bytes were changed to question marks ('?')
编码问题,不影响使用
exiting because client error handler returned INT_EXIT for msgno 20009
FreeTDS: db-lib: exiting because client error handler returned INT_EXIT for msgno 20009
当连接数据库失败,但是没有调用如下代码捕获异常,程序将会自动退出
//捕获错误,避免直接退出程序
dberrhandle(err_handler);
dbmsghandle(msg_handler);