背景介绍
SQLDA(SQL Descriptor Area)是与数据库交互的程序所使用的数据结构。它通常用于在执行 SQL 语句时传递数据、参数和结果。
一个 SQLDA是一种处理SELECT、FETCH或者DESCRIBE语句结果的高级方法。一个 SQLDA把数据中一行的数据及元数据项组合到一个数据结构中。
使用 SQLDA 的程序的一般流程是:
- 准备一个查询,并且为它声明一个游标。
- 为结果行声明一个 SQLDA 。
- 为输入参数声明一个 SQLDA,并且初始化它们(内存分配、参数设置)。
- 用输入 SQLDA 打开一个游标。
- 从游标中取得行,并且把它们存储到一个输出 SQLDA。
- 从输出 SQLDA 读取值到主变量中(必要时使用转换)。
- 关闭游标。
- 关闭为输入 SQLDA 分配的内存区域。
常用的数据结构
SQLDA 使用三种数据结构类型:sqlda_t、sqlvar_t以及struct sqlname。
sqlda_t 结构
结构类型sqlda_t是实际 SQLDA 的类型。它保存一个记录。并且两个或者更多个sqlda_t结构能够以desc_next域中的指针连接成一个链表,这样可以表示一个有序的行集合。因此,当两个或多个行被取得时,应用可以通过沿着每一个sqlda_t节点中的desc_next指针读取它们。
sqlda_t的定义是:
struct sqlda_struct
{
char sqldaid[8];
long sqldabc;
short sqln;
short sqld;
struct sqlda_struct *desc_next;
struct sqlvar_struct sqlvar[1];
};
typedef struct sqlda_struct sqlda_t;
域的含义是:
- sqldaid
它包含一个字符串"SQLDA "。 - sqldabc
它包含已分配空间的尺寸(以字节计)。 - sqln
当它被传递给使用USING关键词的OPEN、DECLARE或者EXECUTE语句时,它包含用于一个参数化查询实例的输入参数的数目。在它被用作SELECT、EXECUTE或FETCH语句的输出时,它的值和sqld一样 - sqld
它包含一个结果集中的域的数量。 - desc_next
如果查询返回不止一个记录,会返回多个链接在一起的 SQLDA 结构,并且desc_next保存一个指向下一个项的指针。sqlvar 这是结果集中列的数组。
sqlvar_t 结构
结构类型sqlvar_t保存一个列值和元数据(例如类型和长度)。
该类型的定义是:
struct sqlvar_struct
{
short sqltype;
short sqllen;
char *sqldata;
short *sqlind;
struct sqlname sqlname;
};
typedef struct sqlvar_struct sqlvar_t;
各个域的含义是:
- sqltype
包含该域的类型标识符。值可以参考ecpgtype.h中的enum ECPGttype。 - sqllen
包含域的二进制长度,例如ECPGt_int是 4 字节。 - sqldata
指向数据。数据的格式在Section 33.4.4中描述。 - sqlind
指向空指示符。0 表示非空,-1 表示空。 - sqlname
域的名称。
struct sqlname 结构
一个struct sqlname结构保持一个列名。它被用作sqlvar_t结构的一个成员。
该结构的定义是:
#define NAMEDATALEN 64
struct sqlname
{
short length;
char data[NAMEDATALEN];
};
各个域的含义是:
- length
包含域名称的长度。 - data
包含实际的域名称。
使用一个 SQLDA 检索一个结果集
通过一个 SQLDA 检索一个查询结果集的一般步骤是:
- 声明一个sqlda_t结构来接收结果集。
- 执行 FETCH/EXECUTE/DESCRIBE 命令来处理一个指定已声明 SQLDA 的查询。
- 通过查看sqlda_t结构的成员sqln来检查结果集中记录的数量。
- 从sqlda_t结构的成员sqlvar[0]、sqlvar[1]等中得到每一列的值。
- 沿着sqlda_t结构的成员desc_next指针到达下一行(sqlda_t)。
- 根据你的需要重复上述步骤。
首先,声明一个sqlda_t结构来接收结果集。
sqlda_t *sqlda1;
接下来,指定一个命令中的 SQLDA。这是一个FETCH命令的例子。
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
运行一个循环顺着链表来检索行。
sqlda_t *cur_sqlda;
for (cur_sqlda = sqlda1;
cur_sqlda != NULL;
cur_sqlda = cur_sqlda->desc_next)
{
...
}
在循环内部,运行另一个循环来检索行中每一列的数据(sqlvar_t结构)。
for (i = 0; i < cur_sqlda->sqld; i++)
{
sqlvar_t v = cur_sqlda->sqlvar[i];
char *sqldata = v.sqldata;
short sqllen = v.sqllen;
...
}
要得到一列的值,应检查sqlvar_t结构的成员sqltype的值。然后,根据列类型切换到一种合适的方法从sqlvar域中复制数据到一个主变量。
char var_buf[1024];
switch (v.sqltype)
{
case ECPGt_char:
memset(&var_buf, 0, sizeof(var_buf));
memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen));
break;
case ECPGt_int: /* integer */
memcpy(&intval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%d", intval);
break;
...
}
使用一个 SQLDA 传递查询参数
使用一个 SQLDA 传递输入参数给一个预备查询的一般步骤是:
-
创建一个预备查询(预备语句)。
-
声明一个 sqlda_t 结构作为输入 SQLDA。
-
为输入 SQLDA 分配内存区域(作为 sqlda_t 结构)。
-
在分配好的内存中设置(复制)输入值。
-
打开一个说明了输入 SQLDA 的游标。
首先,创建一个预备语句。
EXEC SQL BEGIN DECLARE SECTION;
char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)";
EXEC SQL END DECLARE SECTION;
EXEC SQL PREPARE stmt1 FROM :query;
接下来为一个 SQLDA 分配内存,并且在sqlda_t结构的sqln成员变量中设置输入参数的数量。当预备查询要求两个或多个输入参数时,应用必须分配额外的内存空间,空间的大小为 (参数数目 - 1) * sizeof(sqlvar_t)。这里的例子展示了为两个输入参数分配内存空间。
sqlda_t *sqlda2;
sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
sqlda2->sqln = 2; /* 输入变量的数目 */
内存分配之后,把参数值存储到sqlvar[]数组(当 SQLDA 在接收结果集时,这也是用来检索列值的数组)。在这个例子中,输入参数是"postgres"(字符串类型)和1(整数类型)。
sqlda2->sqlvar[0].sqltype = ECPGt_char;
sqlda2->sqlvar[0].sqldata = "postgres";
sqlda2->sqlvar[0].sqllen = 8;
int intval = 1;
sqlda2->sqlvar[1].sqltype = ECPGt_int;
sqlda2->sqlvar[1].sqldata = (char *) &intval;
sqlda2->sqlvar[1].sqllen = sizeof(intval);
通过打开一个游标并且说明之前已经建立好的 SQLDA,输入参数被传递给预备语句。
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
最后,用完输入 SQLDA 后必须显式地释放已分配的内存空间,这与用于接收查询结果的 SQLDA 不同。
free(sqlda2);
完整样例代码
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
EXEC SQL include sqlda.h;
sqlda_t *sqlda1; /* 用于输出的描述符 */
sqlda_t *sqlda2; /* 用于输入的描述符 */
EXEC SQL WHENEVER NOT FOUND DO BREAK;
EXEC SQL WHENEVER SQLERROR STOP;
int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";
int intval;
unsigned long long int longlongval;
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO uptimedb AS con1 USER uptime;
EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
EXEC SQL PREPARE stmt1 FROM :query;
EXEC SQL DECLARE cur1 CURSOR FOR stmt1;
/* 为一个输入参数创建一个 SQLDA 结构 */
sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
sqlda2->sqln = 2; /* 输入变量的数量 */
sqlda2->sqlvar[0].sqltype = ECPGt_char;
sqlda2->sqlvar[0].sqldata = "postgres";
sqlda2->sqlvar[0].sqllen = 8;
intval = 1;
sqlda2->sqlvar[1].sqltype = ECPGt_int;
sqlda2->sqlvar[1].sqldata = (char *) &intval;
sqlda2->sqlvar[1].sqllen = sizeof(intval);
/* 用输入参数打开一个游标。 */
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
while (1)
{
sqlda_t *cur_sqlda;
/* 给游标分配描述符 */
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
for (cur_sqlda = sqlda1 ;
cur_sqlda != NULL ;
cur_sqlda = cur_sqlda->desc_next)
{
int i;
char name_buf[1024];
char var_buf[1024];
/* 打印一行中的每一列。 */
for (i=0 ; i<cur_sqlda->sqld ; i++)
{
sqlvar_t v = cur_sqlda->sqlvar[i];
char *sqldata = v.sqldata;
short sqllen = v.sqllen;
strncpy(name_buf, v.sqlname.data, v.sqlname.length);
name_buf[v.sqlname.length] = '\0';
switch (v.sqltype)
{
case ECPGt_char:
memset(&var_buf, 0, sizeof(var_buf));
memcpy(&var_buf, sqldata, (sizeof(var_buf)<=sqllen ? sizeof(var_buf)-1 : sqllen) );
break;
case ECPGt_int: /* 整数 */
memcpy(&intval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%d", intval);
break;
case ECPGt_long_long: /* 大整数 */
memcpy(&longlongval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%lld", longlongval);
break;
default:
{
int i;
memset(var_buf, 0, sizeof(var_buf));
for (i = 0; i < sqllen; i++)
{
char tmpbuf[16];
snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", (unsigned char) sqldata[i]);
strncat(var_buf, tmpbuf, sizeof(var_buf));
}
}
break;
}
printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
}
printf("\n");
}
}
free(sqlda2);
EXEC SQL CLOSE cur1;
EXEC SQL COMMIT;
EXEC SQL DISCONNECT ALL;
return 0;
}