35.7. 使用 Descriptors 区域

SQLDescriptors 区域是一种更复杂的方法,用于处理SELECTFETCHDESCRIBE语句的结果。 SQLDescriptors 区域将一行数据的数据与元数据项一起分组为一个数据结构。在执行动态 SQL 语句时,元数据特别有用,在这种情况下,可能无法提前知道结果列的性质。 PostgreSQL 提供了两种使用 Descriptors 区域的方法:命名的 SQLDescriptors 区域和 C 结构 SQLDA。

35 .7.1. 命名为 SQLDescriptors 区域

命名的 SQLDescriptors 区域由一个 Headers 和一个或多个项目 Descriptors 区域组成,该 Headers 包含有关整个 Descriptors 的信息,一个或多个项目 Descriptors 区域基本上每个都描述结果行中的一列。

在使用 SQLDescriptors 区域之前,您需要分配一个:

EXEC SQL ALLOCATE DESCRIPTOR identifier;

标识符用作 Descriptors 区域的“变量名”。当您不再需要 Descriptors 时,应该取消分配它:

EXEC SQL DEALLOCATE DESCRIPTOR identifier;

要使用 Descriptors 区域,请在INTO子句中将其指定为存储目标,而不要列出主机变量:

EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;

如果结果集为空,则 Descriptors 区域仍将包含查询的元数据,即字段名称。

对于尚未执行的准备好的查询,可以使用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关键字是可选的,因此使用DESCRIPTORSQL DESCRIPTOR产生了名为 SQL Descriptor Areas。现在它是强制性的,省略SQL关键字将生成 SQLDADescriptors 区域,请参见Section 35.7.2

DESCRIBEFETCH语句中,INTOUSING关键字可以类似地使用:它们在 Descriptors 区域中产生结果集和元数据。

现在如何从 Descriptors 区域中获取数据?您可以将 Descriptors 区域视为具有命名字段的结构。要从标题中检索字段的值并将其存储到主机变量中,请使用以下命令:

EXEC SQL GET DESCRIPTOR name :hostvar = field;

当前,仅定义了一个标题字段:* COUNT *,它告诉您存在多少个项目 Descriptors 区域(即,结果中包含多少列)。主机变量必须为整数类型。要从项目 Descriptors 区域获取字段,请使用以下命令:

EXEC SQL GET DESCRIPTOR name VALUE num :hostvar = field;
  • num *可以是 Literals 整数或包含整数的主机变量。可能的字段是:
  • CARDINALITY(整数)

    • 结果集中的行数
  • DATA

    • 实际数据项(因此,此字段的数据类型取决于查询)
  • DATETIME_INTERVAL_CODE(整数)

    • TYPE9时,DATETIME_INTERVAL_CODE的值将是DATE1TIME2TIMESTAMP3TIME WITH TIME ZONE4TIMESTAMP WITH TIME ZONE5
  • DATETIME_INTERVAL_PRECISION(整数)

    • not implemented
  • INDICATOR(整数)

    • 指示符(指示空值或值截断)
  • KEY_MEMBER(整数)

    • not implemented
  • LENGTH(整数)

    • 基准字符长度
  • NAME(字符串)

    • 列名
  • NULLABLE(整数)

    • not implemented
  • OCTET_LENGTH(整数)

    • 数据字符表示的长度(以字节为单位)
  • PRECISION(整数)

    • 精度(类型numeric)
  • RETURNED_LENGTH(整数)

    • 基准字符长度
  • RETURNED_OCTET_LENGTH(整数)

    • 数据字符表示的长度(以字节为单位)
  • SCALE(整数)

    • 比例尺(对于numeric型)
  • TYPE(整数)

    • 列的数据类型的数字代码

EXECUTEDECLAREOPEN语句中,INTOUSING关键字的作用不同。Descriptors 区域也可以手动构建,以提供查询或游标的 Importing 参数,而USING SQL DESCRIPTOR name是将 Importing 参数传递到参数化查询中的方式。下面是构建命名的 SQLDescriptors 区域的语句:

EXEC SQL SET DESCRIPTOR name VALUE num field = :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;

35 .7.2. SQLDADescriptors 区域

SQLDADescriptors 区域是一种 C 语言结构,还可以用于获取结果集和查询的元数据。一种结构从结果集中存储一条记录。

EXEC SQL include sqlda.h;
sqlda_t         *mysqlda;

EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;

请注意,省略了SQL关键字。 Section 35.7.1中有关INTOUSING关键字的用例的段落也适用于此处。在DESCRIBE语句中,如果使用INTO关键字,则可以完全省略DESCRIPTOR关键字:

EXEC SQL DESCRIBE prepared_statement INTO mysqlda;

使用 SQLDA 的程序的一般流程为:

  • 准备一个查询,并为其声明一个游标。

  • 为结果行声明一个 SQLDA。

  • 为 Importing 参数声明一个 SQLDA,并对其进行初始化(内存分配,参数设置)。

  • 使用 ImportingSQLDA 打开游标。

  • 从游标中获取行,并将其存储到输出 SQLDA 中。

  • 将输出 SQLDA 的值读入主机变量(如有必要,进行转换)。

  • 关闭光标。

  • 释放分配给 ImportingSQLDA 的内存区域。

35 .7.2.1. SQLDA 数据结构

SQLDA 使用三种数据结构类型:sqlda_tsqlvar_tstruct sqlname

Tip

PostgreSQL 的 SQLDA 与 IBM DB2 通用数据库的数据结构相似,因此有关 DB2 的 SQLDA 的一些技术信息可以帮助更好地理解 PostgreSQL 的数据结构。

35 .7.2.1.1. 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

    • 它包含 Literals 字符串"SQLDA "
  • sqldabc

    • 它包含已分配空间的大小(以字节为单位)。
  • sqln

    • 如果使用USING关键字将其传递到OPENDECLAREEXECUTE语句中,则它包含参数化查询的 Importing 参数数。如果将其用作SELECTEXECUTEFETCH语句的输出,则其值与sqld语句相同
  • sqld

    • 它包含结果集中的字段数。
  • desc_next

    • 如果查询返回多个记录,则返回多个链接的 SQLDA 结构,并且desc_next保留指向列表中下一条目的指针。
  • sqlvar

    • 这是结果集中的列的数组。
35 .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

    • 包含字段的类型标识符。有关值,请参见ecpgtype.h中的enum ECPGttype
  • sqllen

    • 包含字段的二进制长度。例如ECPGt_int为 4 个字节。
  • sqldata

  • sqlind

    • 指向空指示器。 0 表示不为空,-1 表示为空。
  • sqlname

    • 字段名称。
35 .7.2.1.3. struct sqlname 结构

struct sqlname结构包含一个列名。它用作sqlvar_t结构的成员。结构的定义为:

#define NAMEDATALEN 64

struct sqlname
{
        short           length;
        char            data[NAMEDATALEN];
};

这些字段的含义是:

  • length

    • 包含字段名称的长度。
  • data

    • 包含实际的字段名称。

35 .7.2.2. 使用 SQLDA 检索结果集

通过 SQLDA 检索查询结果集的一般步骤为:

  • 声明一个sqlda_t结构以接收结果集。

  • 执行FETCH/EXECUTE/DESCRIBE命令以处理指定声明的 SQLDA 的查询。

  • 通过查看sqln(sqlda_t结构的成员)来检查结果集中的记录数。

  • 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;
    ...
}

要获取列值,请检查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;

    ...
}

35 .7.2.3. 使用 SQLDA 传递查询参数

使用 SQLDA 将 Importing 参数传递到准备好的查询的一般步骤是:

  • 创建一个准备好的查询(准备好的语句)

  • 声明一个 sqlda_t 结构作为 ImportingSQLDA。

  • 为 ImportingSQLDA 分配内存区域(作为 sqlda_t 结构)。

  • 在分配的存储器中设置(复制)Importing 值。

  • 通过指定 ImportingSQLDA 打开游标。

这是一个例子。

首先,创建一个准备好的语句。

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结构的成员变量)中设置 Importing 参数的数量。当准备好的查询需要两个或多个 Importing 参数时,应用程序必须分配额外的存储空间,该存储空间由(参数数量-1)* sizeof(sqlvar_t)计算得出。此处显示的示例为两个 Importing 参数分配内存空间。

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 接收结果集时用于检索列值的数组相同.)在此示例中,Importing 参数是具有字符串类型的"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,Importing 参数将传递到准备好的语句。

EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;

最后,在使用 ImportingSQLDA 之后,必须显式释放分配的内存空间,这与用于接收查询结果的 SQLDA 不同。

free(sqlda2);

35 .7.2.4. 使用 SQLDA 的示例应用程序

这是一个示例程序,描述了如何从系统目录中获取由 Importing 参数指定的数据库的访问统计信息。

此应用程序联接数据库 OID 上的两个系统表 pg_database 和 pg_stat_database,并且还获取并显示由两个 Importing 参数(数据库postgres和 OID 1)检索到的数据库统计信息。

首先,为 Importing 声明一个 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;

接下来,在 ImportingSQLDA 中为 Importing 参数 Importing 一些值。为 ImportingSQLDA 分配内存,并将 Importing 参数的数量设置为sqln。将类型,值和值长度存储到sqlvar结构中的sqltypesqldatasqllen中。

/* 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);

设置 ImportingSQLDA 后,使用 ImportingSQLDA 打开一个游标。

/* 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)
    {
        ...

阅读第一条 Logging 的每一列。列数存储在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)\n", name_buf, var_buf, v.sqltype);
        }

处理所有记录后,关闭游标,然后断开与数据库的连接。

EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;

整个程序显示在Example 35.1中。

例 35.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 a 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)
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)