Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

34.7. 使用描述符区域 #

34.7.1. 命名 SQL 描述符区域
34.7.2. SQLDA 描述符区域

SQL 描述符区域是一种处理 SELECTFETCHDESCRIBE 语句结果的更复杂的方法。SQL 描述符区域将一行数据的数据及其元数据项分组为一个数据结构。在执行动态 SQL 语句时元数据特别有用,因为结果列的性质可能无法提前得知。PostgreSQL 提供了两种使用描述符区域的方法:命名的 SQL 描述符区域和 C 结构 SQLDA。

34.7.1. 命名的 SQL 描述符区域 #

命名的 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 关键字是可选的,所以使用 DESCRIPTORSQL DESCRIPTOR 会产生命名的 SQL 描述符区域。现在它是强制性的,省略 SQL 关键字会产生 SQLDA 描述符区域,请参阅 第 34.7.2 节

DESCRIBEFETCH 语句中,INTOUSING 关键字可以类似地使用:它们生成描述符区域中的结果集和元数据。

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

EXEC SQL GET DESCRIPTOR name :hostvar = field;

当前,仅定义了一个标头字段:COUNT,它表示存在多少个项目描述符区域(即结果中包含多少列)。主机变量需要为整数类型。要从项目描述符区域获取字段,请使用以下命令。

EXEC SQL GET DESCRIPTOR name VALUE num :hostvar = field;

num 可以是文字整数或包含整数的主机变量。可能的字段为

CARDINALITY(整数) #

结果集中的行数

DATA #

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

DATETIME_INTERVAL_CODE(整数) #

TYPE9 时,DATETIME_INTERVAL_CODE 对于 DATE 将值为 1、对于 TIME 将值为 2、对于 TIMESTAMP 将值为 3、对于 TIME WITH TIME ZONE 将值为 4、对于 TIMESTAMP WITH TIME ZONE 将值为 5

DATETIME_INTERVAL_PRECISION(整数) #

尚未实现

INDICATOR(整数) #

指示器(指示空值或值截断)

KEY_MEMBER(整数) #

尚未实现

LENGTH(整数) #

字符形式存储的数据长度

NAME(字符串) #

列名

NULLABLE(整数) #

尚未实现

OCTET_LENGTH(整数) #

以字节为单位的字符形式存储的数据长度

PRECISION(整数) #

精度(对于 numeric 类型)

RETURNED_LENGTH(整数) #

字符形式存储的数据长度

RETURNED_OCTET_LENGTH(整数) #

以字节为单位的字符形式存储的数据长度

SCALE(整数) #

刻度(对于 numeric 类型)

TYPE(整数) #

列的数据类型数字代码

EXECUTEDECLAREOPEN 语句中,INTOUSING 关键字的效果不同。也可手动生成一个描述符区域来提供查询或游标的输入参数,USING SQL DESCRIPTOR name 是将输入参数传递到带参数查询中的方法。生成命名 SQL 描述符区域的语句如下

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;

34.7.2. SQLDA 描述符区域 #

SQLDA 描述符区域是一种 C 语言结构,也可用它来获取查询的结果集和元数据。一个结构存储结果集中的一个记录。

EXEC SQL include sqlda.h;
sqlda_t         *mysqlda;

EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;

请注意,SQL 关键字已省略。第 34.7.1 节 中关于 INTOUSING 关键字在 OPEN 语句中的用例的段落也适用于这里,并做了补充。在 DESCRIBE 语句中,如果使用 INTO 关键字,则可以完全省略 DESCRIPTOR 关键字

EXEC SQL DESCRIBE prepared_statement INTO mysqlda;

使用 SQLDA 的程序的一般流程是

  1. 准备一个查询并为其声明一个游标。

  2. 为结果行声明一个 SQLDA。

  3. 为输入参数声明一个 SQLDA 并对它们进行初始化(内存分配、参数设置)。

  4. 用输入 SQLDA 打开游标。

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

  6. 将输出 SQLDA 中的值读到主机变量(如有必要,则进行转换)。

  7. 关闭游标。

  8. 释放分配给输入 SQLDA 的内存区域。

34.7.2.1. SQLDA 数据结构 #

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

提示

PostgreSQL 的 SQLDA 具有与 IBM DB2 Universal Database 中的数据结构类似的数据结构,因此 DB2 SQLDA 中的一些技术信息可以帮助更好地理解 PostgreSQL 的 SQLDA。

34.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 #

它包含字符串常量 "SQLDA "

sqldabc #

它包含已分配空间的大小(字节数)。

sqln #

对于参数化查询(假定它已传递到 OPENDECLAREEXECUTE 语句中,当使用 USING 关键字时),它包含输入参数的数量。假定它用作 SELECTEXECUTEFETCH 语句的输出,其值与 sqld 语句相同。

sqld #

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

desc_next #

如果查询返回多个记录,则会返回多个链接的 SQLDA 结构,且 desc_next 持有一个指向列表中下一个条目的指针。

sqlvar #

这是结果集中列的数组。

34.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 #

指向数据。数据的格式在 第 34.4.4 节 中进行了说明。

sqlind #

指向 null 指示符。0 表示非空,-1 表示 null。

sqlname #

字段的名称。

34.7.2.1.3. struct sqlname 结构 #

一个 struct sqlname 结构持有一个列名。它被用作 sqlvar_t 结构的成员。该结构的定义如下:

#define NAMEDATALEN 64

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

字段的含义是

length #

包含字段名称的长度。

数据 #

包含实际的字段名。

34.7.2.2. 使用 SQLDA 检索结果集 #

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

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

  2. 执行 FETCH/EXECUTE/DESCRIBE 命令,以处理查询,并指定已声明的 SQLDA。

  3. 检查结果集中的记录数,方法是查看 sqlnsqlda_t 结构的成员)。

  4. sqlvar[0]sqlvar[1] 等等(sqlda_t 结构的成员)中获取每个列的值。

  5. 通过跟踪 desc_next 指针(sqlda_t 结构的成员)转到下一行(sqlda_t 结构)。

  6. 根据需要重复上述步骤。

下面是一个使用 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;

    ...
}

34.7.2.3. 使用 SQLDA 传递查询参数 #

使用 SQLDA 向已准备好的查询传递输入参数的一般步骤是

  1. 创建一个已准备好的查询(已准备好的语句)

  2. 将 sqlda_t 结构声明为输入 SQLDA。

  3. 为输入 SQLDA 分配内存区域(作为 sqlda_t 结构)。

  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 在接收结果集时用于检索列值的相同数组。)在此示例中,输入参数是具有字符串类型的 "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);

34.7.2.4. 使用 SQLDA 的示例应用程序 #

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

该应用程序根据数据库 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。将类型、值和值长度存储到 sqltypesqldatasqllen 中的 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 中。(通常,您必须在循环中重复调用 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)\n", name_buf, var_buf, v.sqltype);
        }

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

    EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;

此完整程序展示在 示例 34.1 中。

示例 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)