2025年9月25日: PostgreSQL 18 发布!
支持版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

34.4. 使用宿主变量 #

第 34.3 节 中,您看到了如何在嵌入式 SQL 程序中执行 SQL 语句。其中一些语句只使用了固定值,并且没有提供将用户提供的值插入到语句中或让程序处理查询返回的值的方法。这类语句在实际应用程序中用处不大。本节将详细解释如何使用一种称为“宿主变量”的简单机制在 C 程序和嵌入式 SQL 语句之间传递数据。在嵌入式 SQL 程序中,我们将 SQL 语句视为 C 程序代码中的“访客”,而 C 程序代码是“宿主语言”。因此,C 程序的变量被称为“宿主变量”。

在 PostgreSQL 后端和 ECPG 应用程序之间交换值的另一种方法是使用 SQL 描述符,详见 第 34.7 节

34.4.1. 概述 #

在嵌入式 SQL 中,在 C 程序和 SQL 语句之间传递数据非常简单。您无需让程序将数据粘贴到语句中(这会带来各种复杂性,例如正确引用值),只需将 C 变量名加冒号前缀写在 SQL 语句中即可。例如:

EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);

该语句引用了名为 v1v2 的两个 C 变量,并使用了一个普通的 SQL 字符串字面量,以说明您不必仅限于使用一种数据类型或另一种数据类型。

这种将 C 变量插入 SQL 语句的风格适用于 SQL 语句中需要值表达式的任何地方。

34.4.2. 声明段 #

要将数据从程序传递到数据库(例如作为查询参数),或将数据从数据库传递回程序,用于存储这些数据的 C 变量需要在特殊标记的段中声明,以便嵌入式 SQL 预处理器能够识别它们。

该段以以下内容开始:

EXEC SQL BEGIN DECLARE SECTION;

并以以下内容结束:

EXEC SQL END DECLARE SECTION;

在这两行之间,必须是正常的 C 变量声明,例如:

int   x = 4;
char  foo[16], bar[16];

如您所见,您可以选择性地为变量分配初始值。变量的范围由其声明段在程序中的位置决定。您也可以使用以下语法声明变量,这将隐式创建一个声明段:

EXEC SQL int i = 4;

您可以在一个程序中拥有任意数量的声明段。

声明也会回显到输出文件中,作为普通的 C 变量,因此无需再次声明它们。不打算在 SQL 命令中使用但要在程序中使用(例如,为了在 C 代码中进行计算)的变量可以在这些特殊段之外正常声明。

结构体或联合体的定义也必须包含在 DECLARE 段内。否则,预处理器将无法处理这些类型,因为它不知道其定义。

34.4.3. 检索查询结果 #

现在您应该能够将程序生成的数据传递到 SQL 命令中。但是,如何检索查询结果呢?为此,嵌入式 SQL 提供了常用命令 SELECTFETCH 的特殊变体。这些命令有一个特殊的 INTO 子句,用于指定检索到的值要存储在哪些宿主变量中。SELECT 用于只返回单行的查询,而 FETCH 用于使用游标返回多行的查询。

以下是一个例子

/*
 * assume this table:
 * CREATE TABLE test1 (a int, b varchar(50));
 */

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;

因此,INTO 子句出现在选择列表和 FROM 子句之间。选择列表中的元素数量以及 INTO 后面的列表(也称为目标列表)中的元素数量必须相等。

以下是使用 FETCH 命令的示例:

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL DECLARE foo CURSOR FOR SELECT a, b FROM test;

 ...

do
{
    ...
    EXEC SQL FETCH NEXT FROM foo INTO :v1, :v2;
    ...
} while (...);

这里,INTO 子句出现在所有常规子句之后。

34.4.4. 类型映射 #

当 ECPG 应用程序在 PostgreSQL 服务器和 C 应用程序之间交换值时(例如,从服务器检索查询结果或执行带有输入参数的 SQL 语句时),需要在 PostgreSQL 数据类型和宿主语言变量类型(具体为 C 语言数据类型)之间进行转换。ECPG 的一个主要优点是它在大多数情况下会自动处理这些转换。

在这方面,有两种数据类型:一些简单的 PostgreSQL 数据类型,如 integertext,可以由应用程序直接读写。其他 PostgreSQL 数据类型,如 timestampnumeric,只能通过特殊的库函数访问;参见 第 34.4.4.2 节

表 34.1 显示了哪些 PostgreSQL 数据类型对应于哪些 C 数据类型。当您希望发送或接收给定 PostgreSQL 数据类型的值时,应在声明段中声明一个对应 C 数据类型的 C 变量。

表 34.1. PostgreSQL 数据类型与 C 变量类型之间的映射

PostgreSQL 数据类型 宿主变量类型
smallint short
integer int
bigint long long int
decimal decimal[a]
numeric numeric[a]
real float
double precision double
smallserial short
serial int
bigserial long long int
oid unsigned int
character(n), varchar(n), text char[n+1], VARCHAR[n+1]
name char[NAMEDATALEN]
timestamp timestamp[a]
interval interval[a]
date date[a]
boolean bool[b]
bytea char *, bytea[n]

[a] 此类型只能通过特殊的库函数访问;参见 第 34.4.4.2 节

[b] 如果不是原生类型,则在 ecpglib.h 中声明


34.4.4.1. 处理字符字符串 #

要处理 SQL 字符字符串数据类型,如 varchartext,有两种方法可以声明宿主变量。

一种方法是使用 char[],即 char 数组,这是 C 中处理字符数据最常见的方式。

EXEC SQL BEGIN DECLARE SECTION;
    char str[50];
EXEC SQL END DECLARE SECTION;

请注意,您必须自己处理长度。如果您将此宿主变量用作查询的目标变量,而该查询返回的字符串长度超过 49 个字符,则会发生缓冲区溢出。

另一种方法是使用 VARCHAR 类型,这是 ECPG 提供的一种特殊类型。对于每个变量,VARCHAR 数组的声明将被转换为一个命名的 struct。声明如:

VARCHAR var[180];

将被转换为:

struct varchar_var { int len; char arr[180]; } var;

arr 成员包含字符串,包括一个终止零字节。因此,要将字符串存储在 VARCHAR 宿主变量中,宿主变量的声明长度必须包含零字节终止符。len 成员保存 arr 中存储的字符串长度,不包括终止零字节。当宿主变量用作查询的输入时,如果 strlen(arr)len 不同,则使用较小的值。

VARCHAR 可以大写或小写书写,但不能混合大小写。

charVARCHAR 宿主变量也可以存储其他 SQL 类型的值,这些值将以其字符串形式存储。

34.4.4.2. 访问特殊数据类型 #

ECPG 包含一些特殊类型,可以帮助您轻松地与 PostgreSQL 服务器的一些特殊数据类型进行交互。特别是,它实现了对 numericdecimaldatetimestampinterval 类型的支持。这些数据类型不能有效地映射到原生宿主变量类型(如 intlong long intchar[]),因为它们具有复杂的内部结构。应用程序通过声明特殊类型的宿主变量并使用 pgtypes 库中的函数来处理这些类型。pgtypes 库(详见 第 34.6 节)包含处理这些类型的基础函数,这样您就不必仅仅为了将一个 interval 加到一个 timestamp 而发送一个查询到 SQL 服务器。

接下来的小节描述了这些特殊数据类型。有关 pgtypes 库函数的更多详细信息,请参阅 第 34.6 节

34.4.4.2.1. timestamp, date #

以下是在 ECPG 宿主应用程序中处理 timestamp 变量的模式:

首先,程序必须包含 timestamp 类型的头文件:

#include <pgtypes_timestamp.h>

接下来,在声明段中将宿主变量声明为 timestamp 类型:

EXEC SQL BEGIN DECLARE SECTION;
timestamp ts;
EXEC SQL END DECLARE SECTION;

在将值读入宿主变量后,使用 pgtypes 库函数进行处理。在以下示例中,timestamp 值使用 PGTYPEStimestamp_to_asc() 函数转换为文本(ASCII)格式:

EXEC SQL SELECT now()::timestamp INTO :ts;

printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));

此示例将显示类似以下的输出:

ts = 2010-06-27 18:03:56.949343

此外,DATE 类型也可以以相同的方式处理。程序必须包含 pgtypes_date.h,将宿主变量声明为 date 类型,并使用 PGTYPESdate_to_asc() 函数将 DATE 值转换为文本格式。有关 pgtypes 库函数的更多详细信息,请参阅 第 34.6 节

34.4.4.2.2. interval #

处理 interval 类型也与 timestampdate 类型类似。但是,需要显式地为 interval 类型值分配内存。换句话说,变量的内存空间必须在堆内存中分配,而不是在栈内存中分配。

以下是一个示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_interval.h>

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    interval *in;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    in = PGTYPESinterval_new();
    EXEC SQL SELECT '1 min'::interval INTO :in;
    printf("interval = %s\n", PGTYPESinterval_to_asc(in));
    PGTYPESinterval_free(in);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}
34.4.4.2.3. numeric, decimal #

处理 numericdecimal 类型与 interval 类型类似:需要定义一个指针,在堆上分配一些内存空间,并使用 pgtypes 库函数访问变量。有关 pgtypes 库函数的更多详细信息,请参阅 第 34.6 节

没有专门为 decimal 类型提供函数。应用程序必须使用 pgtypes 库函数将其转换为 numeric 变量以进行进一步处理。

以下是一个处理 numericdecimal 类型变量的示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_numeric.h>

EXEC SQL WHENEVER SQLERROR STOP;

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    numeric *num;
    numeric *num2;
    decimal *dec;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    num = PGTYPESnumeric_new();
    dec = PGTYPESdecimal_new();

    EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec;

    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2));

    /* Convert decimal to numeric to show a decimal value. */
    num2 = PGTYPESnumeric_new();
    PGTYPESnumeric_from_decimal(dec, num2);

    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2));

    PGTYPESnumeric_free(num2);
    PGTYPESdecimal_free(dec);
    PGTYPESnumeric_free(num);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}
34.4.4.2.4. bytea #

处理 bytea 类型与 VARCHAR 类似。对于每个变量,bytea 数组的声明将被转换为一个命名的 struct。声明如:

bytea var[180];

将被转换为:

struct bytea_var { int len; char arr[180]; } var;

arr 成员包含二进制格式数据。与 VARCHAR 不同,它还可以处理 '\0' 作为数据的一部分。数据以十六进制格式在 ecpglib 中进行转换和发送/接收。

注意

bytea 变量仅在 bytea_output 设置为 hex 时才可使用。

34.4.4.3. 带有非原生类型的宿主变量 #

您也可以使用数组、typedef、结构体和指针作为宿主变量。

34.4.4.3.1. 数组 #

数组作为宿主变量有两种用法。第一种是用 char[]VARCHAR[] 存储文本字符串,如 第 34.4.4.1 节中所述。第二种用法是在不使用游标的情况下从查询结果中检索多行。没有数组时,要处理由多行组成的查询结果,需要使用游标和 FETCH 命令。但使用数组宿主变量,可以一次性接收多行。数组的长度必须定义得能够容纳所有行,否则很可能发生缓冲区溢出。

以下示例扫描 pg_database 系统表,并显示所有可用数据库的 OID 和名称:

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    int dbid[8];
    char dbname[8][16];
    int i;
EXEC SQL END DECLARE SECTION;

    memset(dbname, 0, sizeof(char)* 16 * 8);
    memset(dbid, 0, sizeof(int) * 8);

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    /* Retrieve multiple rows into arrays at once. */
    EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;

    for (i = 0; i < 8; i++)
        printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}

此示例显示以下结果。(具体值取决于本地情况。)

oid=1, dbname=template1
oid=11510, dbname=template0
oid=11511, dbname=postgres
oid=313780, dbname=testdb
oid=0, dbname=
oid=0, dbname=
oid=0, dbname=
34.4.4.3.2. 结构体 #

成员名称与查询结果的列名匹配的结构体可以用于一次检索多个列。结构体允许在单个宿主变量中处理多个列值。

以下示例从 pg_database 系统表中检索可用数据库的 OID、名称和大小,并使用 pg_database_size() 函数。在此示例中,使用名为 dbinfo_t 的结构体变量,其成员名称与 SELECT 结果中的每个列匹配,以便在 FETCH 语句中不放置多个宿主变量即可检索一行结果。

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
       long long int size;
    } dbinfo_t;

    dbinfo_t dbval;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size);
    }

    EXEC SQL CLOSE cur1;

此示例显示以下结果。(具体值取决于本地情况。)

oid=1, datname=template1, size=4324580
oid=11510, datname=template0, size=4243460
oid=11511, datname=postgres, size=4324580
oid=313780, datname=testdb, size=8183012

结构体宿主变量“吸收”结构体中字段数量的列。额外的列可以分配给其他宿主变量。例如,上面的程序也可以这样重构,将 size 变量放在结构体之外:

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
    } dbinfo_t;

    dbinfo_t dbval;
    long long int size;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
    }

    EXEC SQL CLOSE cur1;
34.4.4.3.3. Typedefs #

使用 typedef 关键字将新类型映射到已存在的类型。

EXEC SQL BEGIN DECLARE SECTION;
    typedef char mychartype[40];
    typedef long serial_t;
EXEC SQL END DECLARE SECTION;

请注意,您也可以使用:

EXEC SQL TYPE serial_t IS long;

此声明不需要是声明段的一部分;也就是说,您也可以将 typedef 作为常规 C 语句编写。

您声明为 typedef 的任何单词都不能在同一程序中稍后的 EXEC SQL 命令中用作 SQL 关键字。例如,以下代码将不起作用:

EXEC SQL BEGIN DECLARE SECTION;
    typedef int start;
EXEC SQL END DECLARE SECTION;
...
EXEC SQL START TRANSACTION;

ECPG 将报告 START TRANSACTION 的语法错误,因为它不再将 START 识别为 SQL 关键字,而只将其识别为 typedef。(如果您有此类冲突,并且重命名 typedef 似乎不切实际,您可以尝试使用 动态 SQL 来编写 SQL 命令。)

注意

在 v16 之前的 PostgreSQL 版本中,将 SQL 关键字用作 typedef 名称很可能导致与 typedef 本身使用相关的语法错误,而不是名称被用作 SQL 关键字。新行为在新 PostgreSQL 版本中,当使用新关键字重新编译现有 ECPG 应用程序时,不太可能导致问题。

34.4.4.3.4. 指针 #

您可以声明大多数常见类型的指针。但请注意,在没有自动分配的情况下,您不能使用指针作为查询的目标变量。有关自动分配的更多信息,请参阅 第 34.7 节

EXEC SQL BEGIN DECLARE SECTION;
    int   *intp;
    char **charp;
EXEC SQL END DECLARE SECTION;

34.4.5. 处理非原生 SQL 数据类型 #

本节包含关于如何在 ECPG 应用程序中处理非标量和用户定义 SQL 级别数据类型的信息。请注意,这与上一节中描述的非原生类型宿主变量的处理不同。

34.4.5.1. 数组 #

ECPG 不直接支持多维 SQL 级别数组。一维 SQL 级别数组可以映射到 C 数组宿主变量,反之亦然。但是,在创建语句时,ecpg 不知道列的类型,因此无法检查 C 数组是否被输入到相应的 SQL 级别数组中。在处理 SQL 语句的输出时,ecpg 拥有必要的信息,因此会检查两者是否都是数组。

如果查询单独访问数组的元素,则可以避免在 ECPG 中使用数组。然后,应使用可以映射到元素类型的宿主变量。例如,如果列类型是 integer 的数组,则可以使用 int 类型的宿主变量。同样,如果元素类型是 varchartext,则可以使用 char[]VARCHAR[] 类型的宿主变量。

以下是一个示例。假设有以下表:

CREATE TABLE t3 (
    ii integer[]
);

testdb=> SELECT * FROM t3;
     ii
-------------
 {1,2,3,4,5}
(1 row)

以下示例程序检索数组的第 4 个元素并将其存储到 int 类型的宿主变量中:

EXEC SQL BEGIN DECLARE SECTION;
int ii;
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii ;
    printf("ii=%d\n", ii);
}

EXEC SQL CLOSE cur1;

此示例显示以下结果:

ii=4

要将多个数组元素映射到数组类型宿主变量中的多个元素,必须单独管理数组列的每个元素和宿主变量数组的每个元素,例如:

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3];
    ...
}

再次注意:

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* WRONG */
    EXEC SQL FETCH FROM cur1 INTO :ii_a;
    ...
}

在此情况下将无法正确工作,因为您无法直接将数组类型列映射到数组宿主变量。

另一种解决方法是将数组以其外部字符串表示形式存储在 char[]VARCHAR[] 类型的宿主变量中。有关此表示形式的更多详细信息,请参阅 第 8.15.2 节。请注意,这意味着在宿主程序中无法自然地(在不进行进一步解析文本表示的处理的情况下)将数组作为数组来访问。

34.4.5.2. 复合类型 #

ECPG 不直接支持复合类型,但有一个简单的解决方法。可用的解决方法与上面为数组描述的解决方法类似:要么单独访问每个属性,要么使用外部字符串表示。

对于以下示例,假设有以下类型和表:

CREATE TYPE comp_t AS (intval integer, textval varchar(32));
CREATE TABLE t4 (compval comp_t);
INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );

最明显的解决方案是单独访问每个属性。以下程序通过分别选择 comp_t 类型每个属性的数据来检索示例表中的数据:

EXEC SQL BEGIN DECLARE SECTION;
int intval;
varchar textval[33];
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Fetch each element of the composite type column into host variables. */
    EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

    printf("intval=%d, textval=%s\n", intval, textval.arr);
}

EXEC SQL CLOSE cur1;

为了增强此示例,用于在 FETCH 命令中存储值的宿主变量可以收集到一个结构体中。有关结构体形式宿主变量的更多详细信息,请参阅 第 34.4.4.3.2 节。为了切换到结构体,可以修改示例如下:两个宿主变量 intvaltextval 成为 comp_t 结构体的成员,并将结构体指定给 FETCH 命令。

EXEC SQL BEGIN DECLARE SECTION;
typedef struct
{
    int intval;
    varchar textval[33];
} comp_t;

comp_t compval;
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}

EXEC SQL CLOSE cur1;

尽管 FETCH 命令中使用了结构体,但在 SELECT 子句中仍然逐一指定了属性名称。可以通过使用 * 来要求获取复合类型值的全部属性来增强这一点。

...
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}
...

这样,即使 ECPG 不理解复合类型本身,也可以将复合类型几乎无缝地映射到结构体。

最后,还可以将复合类型值以其外部字符串表示形式存储在 char[]VARCHAR[] 类型的宿主变量中。但这样,在宿主程序中就无法轻松地访问值的字段了。

34.4.5.3. 用户定义基本类型 #

ECPG 不直接支持新的用户定义基本类型。您可以使用外部字符串表示和 char[]VARCHAR[] 类型的宿主变量,对于许多类型来说,这种解决方案确实是合适且充分的。

以下是一个使用 第 36.13 节中示例的 complex 数据类型的示例。该类型的外部字符串表示为 (%f,%f),它在 第 36.13 节complex_in()complex_out() 函数中定义。以下示例将复数类型值 (1,1)(3,3) 插入到 ab 列中,然后从表中检索它们。

EXEC SQL BEGIN DECLARE SECTION;
    varchar a[64];
    varchar b[64];
EXEC SQL END DECLARE SECTION;

    EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)');

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :a, :b;
        printf("a=%s, b=%s\n", a.arr, b.arr);
    }

    EXEC SQL CLOSE cur1;

此示例显示以下结果:

a=(1,1), b=(3,3)

另一种解决方法是避免在 ECPG 中直接使用用户定义类型,而是创建一个函数或类型转换,在用户定义类型和 ECPG 可以处理的原生类型之间进行转换。但请注意,类型转换(尤其是隐式类型转换)应非常谨慎地引入到类型系统中。

例如,

CREATE FUNCTION create_complex(r double, i double) RETURNS complex
LANGUAGE SQL
IMMUTABLE
AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;

在此定义之后,以下

EXEC SQL BEGIN DECLARE SECTION;
double a, b, c, d;
EXEC SQL END DECLARE SECTION;

a = 1;
b = 2;
c = 3;
d = 4;

EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));

将与以下具有相同效果:

EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');

34.4.6. 指示器 #

前面的示例不处理 NULL 值。事实上,如果检索示例从数据库中获取 NULL 值,将会引发错误。为了能够将 NULL 值传递给数据库或从数据库中检索 NULL 值,您需要为每个包含数据的宿主变量附加第二个宿主变量规格。第二个宿主变量称为指示器,它包含一个标志,用于指示数据是否为 NULL,在这种情况下,实际宿主变量的值将被忽略。以下是一个正确处理 NULL 值检索的示例:

EXEC SQL BEGIN DECLARE SECTION;
VARCHAR val;
int val_ind;
EXEC SQL END DECLARE SECTION:

 ...

EXEC SQL SELECT b INTO :val :val_ind FROM test1;

如果值不是 NULL,则指示器变量 val_ind 将为零,如果值是 NULL,则指示器变量将为负数。(请参阅 第 34.16 节以启用 Oracle 特定行为。)

指示器还有另一个功能:如果指示器值为正,则表示值不是 NULL,但在存储到宿主变量时被截断了。

如果将参数 -r no_indicator 传递给预处理器 ecpg,它将以“no-indicator”模式工作。在 no-indicator 模式下,如果未指定指示器变量,则 NULL 值将被信号化(输入和输出),对于字符字符串类型,为空字符串;对于整数类型,则为类型的最小值(例如,对于 int,为 INT_MIN)。

提交更正

如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。