SQL 描述符区域(descriptor area)是处理
SELECT、FETCH 或
DESCRIBE 语句结果的一种更完善的方法。
一个 SQL 描述符区域把一行数据的数据
与元数据项组合到一个数据结构中。
在执行动态 SQL 语句时,结果列的性质可能事先是未知的,
这时元数据特别有用。PostgreSQL 提供两种使用
描述符区域的方式:命名的 SQL 描述符区域
和 C 结构 SQLDA。
一个命名的 SQL 描述符区域由一个头部(它包含关于整个描述符的信息) 和一个或多个条目描述符区域组成, 每个条目描述符区域基本上描述结果行中的一列。
在使用一个 SQL 描述符区域之前,你需要先分配一个:
EXEC SQL ALLOCATE DESCRIPTOR identifier;
该标识符用作描述符区域的“变量名”。 当你不再需要该描述符时,应当把它释放掉:
EXEC SQL DEALLOCATE DESCRIPTOR identifier;
要使用一个描述符区域,在 INTO 子句中
把它指定为存储目标,而不是罗列宿主变量:
EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;
如果结果集为空,描述符区域仍然会包含来自查询的元数据, 即字段名。
对于尚未执行的预备查询,
可以使用 DESCRIBE 语句来获取结果集的元数据:
EXEC SQL BEGIN DECLARE SECTION; char *sql_stmt = "SELECT * FROM table1"; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE stmt1 FROM :sql_stmt; EXEC SQL DESCRIBE stmt1 INTO SQL DESCRIPTOR mydesc;
在 PostgreSQL 9.0 之前,SQL 关键字是可选的,
因此使用 DESCRIPTOR 和
SQL DESCRIPTOR 都会生成命名的 SQL 描述符区域。
现在该关键字是必需的,省略 SQL 关键字
将生成 SQLDA 描述符区域,见
Section 34.7.2。
在 DESCRIBE 和 FETCH 语句中,
INTO 和 USING 关键字的用法类似:
它们会把结果集和元数据放入一个描述符区域。
现在如何从描述符区域中取出数据呢?你可以把描述符区域 想象成一个具有命名字段的结构。要把头部中一个字段的值 检索出来并存入一个宿主变量,使用以下命令:
EXEC SQL GET DESCRIPTORname:hostvar=field;
目前只定义了一个头部字段:
COUNT,它说明存在多少个条目
描述符区域(即结果中包含多少列)。
该宿主变量需要是整数类型。要从条目描述符区域中
取出一个字段,使用以下命令:
EXEC SQL GET DESCRIPTORnameVALUEnum:hostvar=field;
num 可以是一个字面整数,
也可以是一个包含整数的宿主变量。可能的字段有:
CARDINALITY (integer) #结果集中行的数量
DATA #实际的数据项(因此,该字段的数据类型取决于查询)
DATETIME_INTERVAL_CODE (integer) #
当 TYPE 为 9 时,
DATETIME_INTERVAL_CODE 的取值为:
1 表示 DATE、
2 表示 TIME、
3 表示 TIMESTAMP、
4 表示
TIME WITH TIME ZONE,或
5 表示
TIMESTAMP WITH TIME ZONE。
DATETIME_INTERVAL_PRECISION (integer) #not implemented
INDICATOR (integer) #指示符(表示一个空值或值被截断)
KEY_MEMBER (integer) #not implemented
LENGTH (integer) #数据的字符长度
NAME (string) #列的名称
NULLABLE (integer) #not implemented
OCTET_LENGTH (integer) #数据的字符表示的字节长度
PRECISION (integer) #
精度(对于 numeric 类型)
RETURNED_LENGTH (integer) #数据的字符长度
RETURNED_OCTET_LENGTH (integer) #数据的字符表示的字节长度
SCALE (integer) #
小数位数(对于 numeric 类型)
TYPE (integer) #该列数据类型的数字编码
在 EXECUTE、DECLARE
和 OPEN
语句中,INTO 和 USING
关键字的效果有所不同。描述符区也可以手工构建,用来为查询或
游标提供输入参数,而
USING SQL DESCRIPTOR
就是把输入参数传入参数化查询的方式。构建一个命名 SQL
描述符区的语句如下:
name
EXEC SQL SET DESCRIPTORnameVALUEnumfield= :hostvar;
PostgreSQL 支持在一条 FETCH 语句中
检索多于一条的记录,并把数据存入宿主变量;
在这种情况下,宿主变量会被假定是一个数组。例如:
EXEC SQL BEGIN DECLARE SECTION; int id[5]; EXEC SQL END DECLARE SECTION; EXEC SQL FETCH 5 FROM mycursor INTO SQL DESCRIPTOR mydesc; EXEC SQL GET DESCRIPTOR mydesc VALUE 1 :id = DATA;
SQLDA 描述符区域是一个 C 语言结构, 它同样可以用来获取查询的结果集和元数据。 一个结构存储结果集中的一条记录。
EXEC SQL include sqlda.h; sqlda_t *mysqlda; EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;
注意这里省略了 SQL 关键字。
Section 34.7.1 中关于
INTO 和 USING
关键字用法的段落在这里同样适用,另有一点补充。
在 DESCRIBE 语句中,
如果使用了 INTO 关键字,
则 DESCRIPTOR 关键字可以完全省略:
EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
使用 SQLDA 的程序的一般流程如下:
准备一个查询,并为它声明一个游标。
为结果行声明一个 SQLDA。
为输入参数声明一个 SQLDA,并对它进行初始化(内存分配、参数设置)。
使用输入 SQLDA 打开一个游标。
从游标取回各行,并把它们存入一个输出 SQLDA。
把输出 SQLDA 中的值读入主变量(必要时进行转换)。
关闭游标。
释放为输入 SQLDA 分配的内存区域。
SQLDA 使用三种数据结构类型:sqlda_t、
sqlvar_t 和 struct sqlname。
PostgreSQL 的 SQLDA 具有与 IBM DB2 Universal Database 中 类似的数据结构,因此关于 DB2 的 SQLDA 的一些技术信息 可以帮助你更好地理解 PostgreSQL 的 SQLDA。
结构类型 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 保存一列的值
以及诸如类型和长度之类的元数据。该类型的定义是:
struct sqlvar_struct
{
short sqltype;
short sqllen;
char *sqldata;
short *sqlind;
struct sqlname sqlname;
};
typedef struct sqlvar_struct sqlvar_t;
各字段的含义是:
通过 SQLDA 检索查询结果集的一般步骤如下:
声明一个 sqlda_t 结构来接收结果集。
执行 FETCH/EXECUTE/DESCRIBE 命令处理查询,并指定已声明的 SQLDA。
查看 sqlda_t 结构的成员 sqln,检查结果集中记录的数量。
从 sqlda_t 结构的 sqlvar[0]、sqlvar[1] 等成员取得各列的值。
沿着 sqlda_t 结构的成员 desc_next 指针转到下一行(下一个 sqlda_t 结构)。
按需重复上述步骤。
下面是一个通过 SQLDA 检索结果集的示例。
首先,声明一个 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_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; /* number of input variables */
内存分配完成后,把参数值存入
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);
下面是一个示例程序,它说明了如何从系统目录中 获取由输入参数指定的数据库的访问统计信息。
这个应用以数据库 OID 为连接条件连接两个系统表
pg_database 和 pg_stat_database,
并抓取和显示由两个输入参数
(数据库 postgres 和 OID 1)
检索到的数据库统计信息。
首先,为输入声明一个 SQLDA,为输出声明一个 SQLDA。
EXEC SQL include sqlda.h; sqlda_t *sqlda1; /* an output descriptor */ sqlda_t *sqlda2; /* an input descriptor */
接下来,连接到数据库,准备一条语句,并为该已准备语句声明一个 游标。
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=? )";
EXEC SQL END DECLARE SECTION;
EXEC SQL CONNECT TO testdb AS con1 USER testuser;
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 中作为输入参数。
为输入 SQLDA 分配内存,并把输入参数的个数设置到
sqln 中。把类型、值和值的长度
分别存入 sqlvar 结构中的
sqltype、sqldata
和 sqllen。
/* Create SQLDA structure for input parameters. */
sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
sqlda2->sqln = 2; /* number of input variables */
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);
设置好输入 SQLDA 之后,使用该输入 SQLDA 打开一个游标。
/* Open a cursor with input parameters. */
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
从打开的游标把行抓取到输出 SQLDA 中。
(一般来说,你必须在循环中反复调用
FETCH,
才能抓取到结果集中的所有行。)
while (1)
{
sqlda_t *cur_sqlda;
/* Assign descriptor to the cursor */
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
接下来,沿着 sqlda_t 结构的链表,
从 SQLDA 中检索被抓取的记录。
for (cur_sqlda = sqlda1 ;
cur_sqlda != NULL ;
cur_sqlda = cur_sqlda->desc_next)
{
...
读取第一条记录中的每一列。列数存储在
sqld 中,
第一列的实际数据存储在 sqlvar[0] 中,
这两者都是 sqlda_t 结构的成员。
/* Print every column in a row. */
for (i = 0; i < sqlda1->sqld; i++)
{
sqlvar_t v = sqlda1->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';
现在,列数据保存在变量 v 中。
查看 v.sqltype 以了解该列的类型,
然后把每个数据复制到宿主变量中。
switch (v.sqltype) {
int intval;
double doubleval;
unsigned long long int longlongval;
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;
...
default:
...
}
printf("%s = %s (type: %d)
", name_buf, var_buf, v.sqltype);
}
在处理完所有记录之后关闭游标,并断开与数据库的连接。
EXEC SQL CLOSE cur1;
EXEC SQL COMMIT;
EXEC SQL DISCONNECT ALL;
完整的程序显示在 Example 34.1 中。
Example 34.1. SQLDA 示例程序
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
EXEC SQL include sqlda.h;
sqlda_t *sqlda1; /* descriptor for output */
sqlda_t *sqlda2; /* descriptor for input */
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;
/* Create an SQLDA structure for an input parameter */
sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
sqlda2->sqln = 2; /* a number of input variables */
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);
/* Open a cursor with input parameters. */
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
while (1)
{
sqlda_t *cur_sqlda;
/* Assign descriptor to the cursor */
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];
/* Print every column in a row. */
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: /* integer */
memcpy(&intval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%d", intval);
break;
case ECPGt_long_long: /* bigint */
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");
}
}
EXEC SQL CLOSE cur1;
EXEC SQL COMMIT;
EXEC SQL DISCONNECT ALL;
return 0;
}
这个例子的输出看起来应该类似于下面这样 (某些数字会有所不同)。
oid = 1 (type: 1)
datname = template1 (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = t (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig = (type: 1)
datacl = {=c/uptime,uptime=CTc/uptime} (type: 1)
datid = 1 (type: 1)
datname = template1 (type: 1)
numbackends = 0 (type: 5)
xact_commit = 113606 (type: 9)
xact_rollback = 0 (type: 9)
blks_read = 130 (type: 9)
blks_hit = 7341714 (type: 9)
tup_returned = 38262679 (type: 9)
tup_fetched = 1836281 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)
oid = 11511 (type: 1)
datname = postgres (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = f (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig = (type: 1)
datacl = (type: 1)
datid = 11511 (type: 1)
datname = postgres (type: 1)
numbackends = 0 (type: 5)
xact_commit = 221069 (type: 9)
xact_rollback = 18 (type: 9)
blks_read = 1176 (type: 9)
blks_hit = 13943750 (type: 9)
tup_returned = 77410091 (type: 9)
tup_fetched = 3253694 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)