Postgresql 中文操作指南

36.7. Using Descriptor Areas #

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

36.7.1. Named SQL Descriptor Areas #

命名的 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 描述器区域,请参阅 Section 36.7.2

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

现在,您怎样才能从描述符区域获取数据呢?您可以将描述符区域视为一个带有命名字段的结构。要从页眉中检索字段的值并将其存储到宿主变量中,请使用以下命令:

EXEC SQL GET DESCRIPTOR name :hostvar = field;

当前,只定义了一个页眉字段:COUNT,它告诉有多少个项目描述符区域(即结果中包含多少列)。宿主变量需要是整数类型。要从项目描述符区域获取字段,请使用以下命令:

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

num 可以是文字整数或包含整数的宿主变量。可能的字段有:

  • CARDINALITY (integer) #

    • 结果集中的行数

  • DATA #

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

  • DATETIME_INTERVAL_CODE (integer) #

    • TYPE9 时,DATETIME_INTERVAL_CODE 将对 DATE 具有 1 的值,对 TIME 具有 2 的值,对 TIMESTAMP 具有 3 的值,对 TIME WITH TIME ZONE 具有 4 的值,或对 TIMESTAMP WITH TIME ZONE 具有 5 的值。

  • DATETIME_INTERVAL_PRECISION (integer) #

    • not implemented

  • INDICATOR (integer) #

    • 指示符(指示空值或值被截断)

  • KEY_MEMBER (integer) #

    • not implemented

  • LENGTH (integer) #

    • 数据的字符长度

  • NAME (string) #

    • name of the column

  • NULLABLE (integer) #

    • not implemented

  • OCTET_LENGTH (integer) #

    • 数据以字节表示的字符长度

  • PRECISION (integer) #

    • precision (for type numeric)

  • RETURNED_LENGTH (integer) #

    • 数据的字符长度

  • RETURNED_OCTET_LENGTH (integer) #

    • 数据以字节表示的字符长度

  • SCALE (integer) #

    • scale (for type numeric)

  • TYPE (integer) #

    • 列数据类型数值代码

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;

36.7.2. SQLDA Descriptor Areas #

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

EXEC SQL include sqlda.h;
sqlda_t         *mysqlda;

EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;

请注意,SQL 关键字已省略。 Section 36.7.1 中关于 INTOUSING 关键字用例的段落也适用于此,但有一个附加内容。如果使用 INTO 关键字,则可以在 DESCRIBE 语句中完全省略 DESCRIPTOR 关键字:

EXEC SQL DESCRIBE prepared_statement INTO mysqlda;

使用 SQLDA 的程序的一般流如下:

36.7.2.1. SQLDA Data Structure #

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

Tip

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 关键字将输入参数传递到 OPENDECLAREEXECUTE 语句中。以防用作 SELECTEXECUTEFETCH 语句的输出,其值与 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;

字段的含义为:

  • sqltype #

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

  • sqllen #

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

  • sqldata #

  • sqlind #

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

  • sqlname #

    • 字段的名称。

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

#define NAMEDATALEN 64

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

字段的含义为:

  • length #

    • 包含字段名称的长度。

  • data #

    • 包含实际的字段名称。

36.7.2.2. Retrieving a Result Set Using an SQLDA #

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

以下是一个通过 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;

    ...
}

36.7.2.3. Passing Query Parameters Using an SQLDA #

通过 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);

36.7.2.4. A Sample Application Using 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;

完整的程序显示在 Example 36.1 中。

Example 36.1. Example SQLDA Program

#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)
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)
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)