## 36.7.使用描述符区域 [36.7.1. 命名SQL描述符区域](ecpg-descriptors.html#ECPG-NAMED-DESCRIPTORS) [36.7.2. SQLDA描述符区域](ecpg-descriptors.html#ECPG-SQLDA-DESCRIPTORS) SQL描述符区域是处理查询结果的更复杂的方法`选择`,`取来`或者`描述`陈述SQL描述符区域将一行数据的数据与元数据项组合到一个数据结构中。元数据在执行动态SQL语句时特别有用,因为在动态SQL语句中,可能无法提前知道结果列的性质。PostgreSQL提供了两种使用描述符区域的方法:命名SQL描述符区域和C结构SQLDAs。 ### 36.7.1.命名SQL描述符区域 一个命名的SQL描述符区域由一个头部和一个或多个条目描述符区域组成,前者包含有关整个描述符的信息,后者基本上每个描述结果行中的一列。 在使用SQL描述符区域之前,需要分配一个: ``` EXEC SQL ALLOCATE DESCRIPTOR identifier; ``` 标识符用作描述符区域的“变量名”。当您不再需要描述符时,应该取消分配它: ``` EXEC SQL DEALLOCATE DESCRIPTOR identifier; ``` 要使用描述符区域,请将其指定为`进入`子句,而不是列出主机变量: ``` EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc; ``` 如果结果集为空,描述符区域仍将包含来自查询的元数据,即字段名。 对于尚未执行的准备好的查询`描述`语句可用于获取结果集的元数据: ``` 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`关键字是可选的,所以使用`描述符`和`SQL描述符`生成命名的SQL描述符区域。现在它是强制性的,省略了`SQL`关键字生成SQLDA描述符区域,请参见[第36.7.2节](ecpg-descriptors.html#ECPG-SQLDA-DESCRIPTORS). 在里面`描述`和`取来`声明`进入`和`使用`关键字也可以用于类似的操作:它们在描述符区域中生成结果集和元数据。 现在,如何从描述符区域获取数据?可以将描述符区域视为具有命名字段的结构。要从标头中检索字段值并将其存储到主机变量中,请使用以下命令: ``` EXEC SQL GET DESCRIPTOR name :hostvar = field; ``` 目前,只定义了一个标题字段:*`计数`*,表示存在多少项描述符区域(即结果中包含多少列)。主机变量必须是整数类型。要从项目描述符区域获取字段,请使用以下命令: ``` EXEC SQL GET DESCRIPTOR name VALUE num :hostvar = field; ``` *`号码`*可以是文字整数,也可以是包含整数的宿主变量。可能的字段包括: `基数`(整数) 结果集中的行数 `数据` 实际数据项(因此,此字段的数据类型取决于查询) `日期时间间隔代码`(整数) 什么时候`类型`是`9`, `日期时间间隔代码`将有价值`1.`对于`日期`, `2.`对于`时间`, `3.`对于`时间戳`, `4.`对于`带时区的时间`或`5.`对于`带时区的时间戳`. `日期时间间隔精度`(整数) 未实施 `指示信号`(整数) 指示符(指示空值或值截断) `关键成员`(整数) 未实施 `长`(整数) 以字符为单位的基准长度 `名称`(字符串) 列的名称 `可为空`(整数) 未实施 `八位组长度`(整数) 数据的字符表示长度(字节) `精确`(整数) 精度(用于类型)`数字的`) `返回长度`(整数) 以字符为单位的基准长度 `返回的八位字节长度`(整数) 数据的字符表示长度(字节) `规模`(整数) 比例(用于类型)`数字的`) `类型`(整数) 列的数据类型的数字代码 在里面`处决`, `声明`和`打开`声明的影响`进入`和`使用`关键词是不同的。还可以手动构建描述符区域,以提供查询或光标的输入参数`使用SQL描述符*`名称`*`是将输入参数传递到参数化查询的方法。构建命名SQL描述符区域的语句如下: ``` EXEC SQL SET DESCRIPTOR name VALUE num field = :hostvar; ``` PostgreSQL支持一次检索多条记录`取来`语句,并将数据存储在主机变量中。在本例中,假设变量是数组。例如。: ``` 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; ``` ### 36.7.2.SQLDA描述符区域 SQLDA描述符区域是一种C语言结构,也可用于获取查询的结果集和元数据。一个结构存储结果集中的一条记录。 ``` EXEC SQL include sqlda.h; sqlda_t *mysqlda; EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda; ``` 请注意`SQL`关键字被省略。关于`进入`和`使用`关键词[第36.7.1节](ecpg-descriptors.html#ECPG-NAMED-DESCRIPTORS)也可以在这里添加一个选项。在一个`描述`声明`描述符`如果`进入`使用的关键字是: ``` EXEC SQL DESCRIBE prepared_statement INTO mysqlda; ``` 使用SQLDA的程序的一般流程是: 1. 准备一个查询,并为其声明一个游标。 2. 为结果行声明SQLDA。 3. 为输入参数声明一个SQLDA,并初始化它们(内存分配、参数设置)。 4. 使用输入SQLDA打开光标。 5. 从游标获取行,并将它们存储到输出SQLDA中。 6. 将输出SQLDA中的值读入主机变量(必要时进行转换)。 7. 关闭光标。 8. 释放为输入SQLDA分配的内存区域。 #### 36.7.2.1.SQLDA数据结构 SQLDA使用三种数据结构类型:`sqlda_t`, `sqlvar_t`和`结构sqlname`. ### 提示 PostgreSQL的SQLDA与IBM DB2 Universal Database中的数据结构相似,因此有关DB2的SQLDA的一些技术信息可以帮助更好地理解PostgreSQL。 ##### 36.7.2.1.1.sqlda_t结构 结构类型`sqlda_t`是实际SQLDA的类型。它保持着一项记录。两个或更多`sqlda_t`结构可以在链接列表中连接,指针位于`下一步`字段,从而表示一个有序的行集合。因此,当获取两行或更多行时,应用程序可以按照`下一步`每一个指针`sqlda_t`节点。 定义`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` 它包含参数化查询的输入参数数,以防将其传递到`打开`, `声明`或`处决`使用`使用`关键词。以防它被用作`选择`, `处决`或`取来`语句,其值与`sqld`陈述 `sqld` 它包含结果集中的字段数。 `下一步` 如果查询返回多条记录,则返回多个链接的SQLDA结构,并且`下一步`保存指向列表中下一项的指针。 `sqlvar` 这是结果集中列的数组。 ##### 36.7.2.1.2.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` 包含字段的类型标识符。有关值,请参见`枚举ECPGttype`在里面`ecpgtype。H`. `sqllen` 包含字段的二进制长度。e、 例如,4字节用于`ECPGt_int`. `sqldata` 指向数据。数据格式如中所述[第36.4.4节](ecpg-variables.html#ECPG-VARIABLES-TYPE-MAPPING). `sqlind` 指向空指示器。0表示不为空,-1表示为空。 `sqlname` 字段的名称。 ##### 36.7.2.1.3.结构sqlname结构 A.`结构sqlname`结构包含一个列名。它被用作`sqlvar_t`结构该结构的定义是: ``` #define NAMEDATALEN 64 struct sqlname { short length; char data[NAMEDATALEN]; }; ``` 这些字段的含义是: `长` 包含字段名的长度。 `数据` 包含实际的字段名。 #### 36.7.2.2.使用SQLDA检索结果集 通过SQLDA检索查询结果集的一般步骤如下: 1. 宣布`sqlda_t`结构来接收结果集。 2. 处决`取来`/`处决`/`描述`用于处理指定声明的SQLDA的查询的命令。 3. 查看结果集中的记录数`sqln`,是`sqlda_t`结构 4. 从中获取每列的值`sqlvar[0]`, `sqlvar[1]`等的成员`sqlda_t`结构 5. 去下一排(`sqlda_t`结构)通过遵循`下一步`指针,是`sqlda_t`结构 6. 根据需要重复上述步骤。 下面是一个通过SQLDA检索结果集的示例。 首先,申报`sqlda_t`结构来接收结果集。 ``` sqlda_t *sqlda1; ``` 接下来,在命令中指定SQLDA。这是一个`取来`命令示例。 ``` 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; ... } ``` 要获取列值,请检查`sqltype`值,是`sqlvar_t`结构然后,根据列类型,切换到适当的方式,从`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; ... } ``` #### 36.7.2.3.使用SQLDA传递查询参数 使用SQLDA向准备好的查询传递输入参数的一般步骤如下: 1. 创建准备好的查询(准备好的语句) 2. 声明一个sqlda_t结构作为输入SQLDA。 3. 分配内存区域(如sqlda)\_输入SQLDA的。 4. 在分配的内存中设置(复制)输入值。 5. 通过指定输入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分配内存,并在中设置输入参数的数量`sqln`,是`sqlda_t`结构当准备好的查询需要两个或多个输入参数时,应用程序必须分配额外的内存空间,该空间由(参数数量-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接收结果集时用于检索列值的数组相同。)在本例中,输入参数为`“博士后”`,具有字符串类型,以及`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; ``` 最后,在使用输入SQLDAs之后,必须显式释放分配的内存空间,这与用于接收查询结果的SQLDAs不同。 ``` free(sqlda2); ``` #### 36.7.2.4.使用SQLDA的示例应用程序 下面是一个示例程序,它描述了如何从系统目录中获取由输入参数指定的数据库访问统计信息。 此应用程序连接两个系统表pg\_数据库和pg\_统计\_数据库 OID 上的数据库,还获取并显示由两个输入参数检索的数据库统计信息(一个数据库`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`.将类型、值和值长度存储到`sqltype`,`sqldata`, 和`sqllen`在里面`sqlvar`结构体。 ``` /* 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。(通常,您必须致电`拿来`在循环中重复,以获取结果集中的所有行。) ``` while (1) { sqlda_t *cur_sqlda; /* Assign descriptor to the cursor */ EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1; ``` 接下来,通过遵循`sqlda_t`结构体。 ``` 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、 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)\n", name_buf, var_buf, v.sqltype); } ``` 处理完所有记录后关闭光标,并断开与数据库的连接。 ``` EXEC SQL CLOSE cur1; EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; ``` 整个程序如图所示[例36.1](ecpg-descriptors.html#ECPG-SQLDA-EXAMPLE-EXAMPLE). **例36.1.SQLDA程序示例** ``` #include #include #include #include #include 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 ; isqld ; 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) datconnlimit = -1 (type: 5) datlastsysoid = 11510 (type: 1) 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) datconnlimit = -1 (type: 5) datlastsysoid = 11510 (type: 1) 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) ```