33.4. 使用主变量

33.4.1. 概述
33.4.2. 声明小节
33.4.3. 兼容 Oracle的主变量声明
33.4.4. 检索查询结果
33.4.5. 类型映射
33.4.6. 处理非简单 SQL 数据类型
33.4.7. 指示符

Section 33.3中,你了解了如何从一个嵌入式 SQL 程序执行 SQL 语句。某些那种语句只使用固定值并且没有提供方法来插入用户提供的值到语句中或者让程序处理查询返回的值。那种语句在实际应用中其实没有什么用处。这一节详细解释了如何使用一种简单的机制(主变量)在 C 程序和嵌入式 SQL 语句之间传递数据。在一个嵌入式 SQL 程序中,我们认为 SQL 语句是 C 程序代码中的客人,而 C 代码是主语言。因此 C 程序的变量被称为主变量

另一种在 LightDB 后端和 ECPG(Oracle Pro*c兼容)应用之间交换值的方式是使用 SQL 描述符,它在Section 33.7中介绍。

33.4.1. 概述

在嵌入式 SQL 中进行 C 程序和 SQL 语句间的数据传递特别简单。我们不需要让程序把数据粘贴到语句(这会导致很多复杂性,例如正确地引用值),我们可以简单地在 SQL 语句中写 C 变量的名称,只要在它前面放上一个冒号。例如:

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

这个语句引用了两个 C 变量(名为v1v2)并且还使用了一个常规的 SQL 字符串来说明你没有被限制于使用某一种数据。

这种在 SQL 语句中插入 C 变量的风格可以用在 SQL 语句中每一个应该出现值表达式的地方。

33.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 命令中使用的变量可以正常地在这些特殊节之外声明。

一个结构或联合的定义也必须被列在一个DECLARE节中。否则预处理器无法处理这些类型,因为它不知道它们的定义。

33.4.3. 兼容 Oracle的主变量声明

Oracle 在使用主变量时,除支持在 Section 33.4.2 中描述的两种方法外,还支持将特定规则的 C 变量声明作为主变量使用 的场景。现在,LightDB 支持了 Oracle 该使用方法,并进一步的讲解这种用法。分为声明的 C 变量作为主变量、结构或联合作为主变量和函数中参数作为主变量三部分。

注意:ecpg_kwlist.h 和 c_kwlist.h 头文件中的列举的所有关键字,在 ecpg(Oracle Pro*c兼容) 中不作为主变量,这是由 ecpg(Oracle Pro*c兼容) 限制决定的。

33.4.3.1. 声明的 C 变量作为主变量

.pgc 文件 或者 .h 文件(头文件支持循环引用)中定义的的 C 变量数据类型int、short、long、long long、bool、double、float、char、unsigned、signed都可以作为主变量使用。 使用时需要受到 ECPG(Oracle Pro*c兼容) 本身主变量的规则限制,下面我们讲解这些限制:

  • int 类型不声明在 section 中,作为主变量, 支持的格式:

    int a;
    int *a;
    int a[];
    

    short、bool、double、float 类型与 int 类型支持的格式相同。

  • long 类型不声明在 section 中,作为主变量, 支持的格式:

    long a;
    long *a;
    long a[];
    long int a;
    long int *a;
    long int a[];
    

    long short 与 long int 类型支持的格式相同。

  • long long 类型不声明在 section 中,作为主变量, 支持的格式:

    long long a;
    long long *a;
    long long a[];
    long long int a;
    long long int *a;
    long long int a[];
    

    long long short 与 long long int 类型支持的格式相同。

  • char 类型不声明在 section 中,作为主变量, 支持的格式:

    char a;
    char *a;
    char a[];
    char **a;
    char *a[];
    

  • unsigned 类型不声明在 section 中,作为主变量, 支持的格式:

    unsigned a;
    unsigned short a;
    unsigned short int a;
    unsigned int a;
    unsigned long int a;
    unsigned long long a;
    unsigned long long int a;
    unsigned char a;
    

    指针支持格式,与类型不加 unsigned 相同。

  • signed 类型不声明在 section 中,作为主变量, 支持的格式:

    signed short a;
    signed short int a;
    signed int a;
    signed long int a;
    signed long long a;
    signed long long int a;
    signed char a;
    signed bool a;
    signed double;
    

    指针支持格式,与类型不加 signed 相同。和 unsigned 不同在于,signed 它本身不能作为主变量。

33.4.3.2. 结构或联合作为主变量

C 变量数据类型struct、union、enum、typedef 都可以作为主变量使用。结构或联合的定义 .pgc 文件中,或者 .h 文件中 。结构体中 C 变量被当作主变量, 支持的支持的格式和 Section 33.4.3.1 中规定的规则一致。

33.4.3.3. 函数中参数作为主变量

函数参数可以作为主变量被解析,支持的格式和 Section 33.4.3.1 中规定的规则一致。

声明或定义函数时,如果函数之前存在宏定义,函数中的参数(C 变量)不能作为主变量使用。

声明或定义函数时,如果函数参数格式不在 Section 33.4.3.1 中规定范围内,该变量为 C 变量。 比如:int、short、bool、double、float的二维数组。

int **a;
int *a[];
int a[][];

33.4.4. 检索查询结果

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

这里是一个例子:

/*
 * 假定有这个表:
 * 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子句出现在所有正常子句的后面。 INTO后面的主变量支持带冒号和不带冒号两种写法,如INTO :v1INTO v1

33.4.5. 类型映射

当 ECPG(Oracle Pro*c兼容)应用在 LightDB 服务器和 C 应用之间交换值时(例如从服务器检索查询结果时或者用输入参数执行 SQL 语句时),值需要在 LightDB 数据类型和主语言变量类型(具体来说是 C 语言数据类型)之间转换。ECPG(Oracle Pro*c兼容) 的要点之一就是它会在大多数情况下自动搞定这种转换。

在这方面有两类数据类型:一些简单 LightDB 数据类型(例如integertext)可以被应用直接读取和写入。其他 LightDB 数据类型(例如timestampnumeric)只能通过特殊库函数访问,见Section 33.4.5.2

Table 33.1展示了哪种 LightDB 数据类型对应于哪一种 C 数据类型。当你希望发送或接收一种给定 LightDB 数据类型的值时,你应该在声明节中声明一个具有相应 C 数据类型的 C 变量。

Table 33.1. 在 LightDB 数据类型和 C 变量类型之间映射

LightDB 数据类型主变量类型
smallintshort
integerint
bigintlong long int
decimaldecimal[a]
numericnumeric[a]
realfloat
double precisiondouble
smallserialshort
serialint
bigseriallong long int
oidunsigned int
character(n), varchar(n), textchar[n+1], VARCHAR[n+1]
namechar[NAMEDATALEN]
timestamptimestamp[a]
intervalinterval[a]
datedate[a]
booleanbool[b]
byteachar *, bytea[n]

[a] 这种类型只能通过特殊的库函数访问,见Section 33.4.5.2

[b] 如果不是本地化类型,则声明在ecpglib.h


33.4.5.1. 处理字符串

要处理 SQL 字符串数据类型(例如varchar以及text),有两种可能的方式来声明主变量。

一种方式是使用char[](一个char字符串),这是在 C 中处理字符数据最常见的方式。

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

注意你必须自己照看长度。如果你把这个主变量用作一个查询的目标变量并且该查询返回超过 49 个字符的字符串,那么将会发生缓冲区溢出。

另一种方式是使用VARCHAR类型,它是 ECPG(Oracle Pro*c兼容)提供的一种特殊类型。在一个VARCHAR类型数组上的定义会被转变成一个命名的struct。这样一个声明:

VARCHAR var[180];

会被转变成:

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

成员arr容纳包含一个终止零字节的字符串。因此,要在一个VARCHAR主变量中存储一个字符串,该主变量必须被声明为具有包括零字节终止符的长度。成员len保存存储在arr中的字符串的长度,不包括终止零字节。当一个主变量被用做一个查询的输入时,如果strlen(arr)len不同,将使用短的那一个。

VARCHAR可以被写成大写或小写形式,但是不能大小写混合。

charVARCHAR主变量也可以保存其他 SQL 类型的值,它们将被存储为字符串形式。

33.4.5.2. 访问特殊数据类型

ECPG(Oracle Pro*c兼容)包含一些特殊类型帮助你容易地与来自 LightDB 服务器的一些特殊数据类型交互。特别地,它已经实现了对于numericdecimaldatetimestamp以及interval类型的支持。这些数据类型无法有效地被映射到原始的主变量类型(例如intlong long int或者char[]),因为它们有一种复杂的内部结构。应用通过声明特殊类型的主变量以及使用 pgtypes 库中的函数来处理这些类型。pgtypes 库(在Section 33.6中详细描述)包含了处理这些类型的基本函数,这样你不需要仅仅为了给一个时间戳增加一个时段而发送一个查询给 SQL 服务器。

下面的小节描述了这些特殊数据类型。关于 pgtypes 库函数的更多细节,请参考Section 33.6

33.4.5.2.1. timestamp, date

这里有一种在 ECPG(Oracle Pro*c兼容)主应用中处理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()函数转变成一种文本形式。关于 pgtypes 库函数的更多细节,请参考Section 33.6

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

33.4.5.2.3. numeric, decimal

numericdecimal类型的处理类似于interval类型:需要定义一个指针、在堆上分配一些内存空间并且使用 pgtypes 库函数访问该变量。关于 pgtypes 库函数的更多细节,请参考Section 33.6

pgtypes 库没有特别为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));

    /* 将一个decimal转变成numeric以显示一个decimal值。 */
    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;
}

33.4.5.2.4. bytea

bytea类型的处理与VARCHAR相似。 类型bytea的数组上的定义被转换为每个变量的命名结构。声明类似于:

bytea var[180];

会被转变成:

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

成员 arr 承载二进制格式数据。 不像VARCHAR,它还可以作为数据的一部分处理 '\0' 。 数据往/来转换为十六进制格式,并通过 ecpglib 发送/接收。

Note

bytea 变量只有在 bytea_output 被设置为 hex时才能够使用.

33.4.5.3. 非简单类型的主变量

你也可以把数组、typedefs、结构和指针用作主变量。

33.4.5.3.1. 数组

将数组用作主变量有两种情况。第一种如Section 33.4.5.1所述,是一种将一些文本字符串存储在char[]VARCHAR[]中的方法。第二种是不用一个游标从一个查询结果中检索多行。如果没有一个数组,要处理由多个行组成的查询结果,我们需要使用一个游标以及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;

    /* 一次检索多行到数组中。 */
    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=

33.4.5.3.2. 结构体

一个成员名称匹配查询结果列名的结构可以被用来一次检索多列。该结构使得我们能够在一个单一主变量中处理多列值。

下面的例子从pg_database系统表以及使用pg_database_size() 函数检索可用数据库的 OID、名称和尺寸。在这个例子中,一个成员名匹配SELECT 结果的每一列的结构变量dbinfo_t被用来检索结果行,而不需要把多个主变量放在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;

/* 在达到结果集末尾时,跳出 while 循环 */
EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* 将多列取到一个结构中。 */
    EXEC SQL FETCH FROM cur1 INTO :dbval;

    /* 打印该结构的成员。 */
    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;

/* 在达到结果集末尾时,跳出 while 循环 */
EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* 将多列取到一个结构中。 */
    EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

    /* 打印该结构的成员。 */
    printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
}

EXEC SQL CLOSE cur1;

支持结构体数组的批量插入,例如

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

/* 结构体数组 */
dbinfo_t dbval[10];

EXEC SQL END DECLARE SECTION;

/* 初始化结构体数据 */
for(int i = 0; i > 10; i ++) {
  dbval[i].oid = i;
  sprintf(dbval[i].datname, "name: %d", i);
}

/* 这里会插入10条数据 */
EXEC SQL INSERT INTO test_table(oid,datname) VALUES(:dbval);

这个特性有如下限制

  • 仅支持结构体数组常量,且插入数组的全部数据, 比如struct test_struct testdata[10]; 仅支持插入10条数据,不支持插入数组中一部分。 所以开发者需要确保数组中的结构体全部都正确初始化。
  • 绑定参数会按照结构体成员数量展开,比如上面例子中,EXEC SQL INSERT INTO test_table(oid,datname) VALUES(:dbval),因为结构体有2个成员, 所以ecpg编译器会转换成 INSERT INTO test_table(oid,datname) VALUES(?, ?),使用两个占位符去绑定参数。 所以如果写成 EXEC SQL INSERT INTO test_table(oid) VALUES(:dbval);,则在后续运行时会报错误。
  • 此特性简单把一条语句转换成多条语句执行, 在AUTOCOMMITOFF的时候,如果中间有一条数据插入失败,则会全部回滚。 而AUTOCOMMITON的时候,会部分插入成功(不会回滚)。

33.4.5.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;

这种声明不需要位于一个声明节之中。

33.4.5.3.4. 指针

你可以声明最常见类型的指针。不过注意,你不能使用指针作为不带自动分配内存的查询的目标变量。关于自动分配内存的详情请参考Section 33.7

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

33.4.6. 处理非简单 SQL 数据类型

这一节包含关于如何处理 ECPG(Oracle Pro*c兼容)应用中非标量以及用户定义的 SQL 级别数据类型。注意这和上一节中描述的简单类型主变量的处理有所不同。

33.4.6.1. 数组

ECPG(Oracle Pro*c兼容)中不直接支持 SQL 级别的多维数组。一维 SQL 数组可以被映射到 C 数组主机变量,反之 亦然。不过,在创建一个语句时,ecpg(Oracle Pro*c兼容)并不知道列的类型,因此它无法检查一个 C 数组否是一个 SQL 数组的输入。在处理一个 SQL 语句的输出时,ecpg(Oracle Pro*c兼容) 有必需的信息并且进而检查是否两者都是 数组。

如果一个查询个别地访问一个数组的元素,那么这可以避免使用 ECPG(Oracle Pro*c兼容)中的数组。然后,应该使用一个能被映射到该元素类型的类型的主变量。例如,如果一个列类型是integer数组,可以使用一个类型int的主变量。还有如果元素类型是varchartext,可以使用一个类型char[]VARCHAR[]的主变量。

这里是一个例子。假定有下面的表:

CREATE TABLE t3 (
    ii integer[]
);

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

下面的例子程序检索数组的第四个元素并且把它存储到一个类型为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)
{
    /* 错误 */
    EXEC SQL FETCH FROM cur1 INTO :ii_a;
    ...
}

在这种情况中不会正确工作,因为你无法把一个数组类型列直接映射到一个数组主变量。

另一种变通方案是在类型char[]VARCHAR[]的主变量中存储数组的外部字符串表达。关于这种表达的详情请见Section 9.14.2。注意这意味着该数组无法作为一个主程序中的数组被自然地访问(没有解析文本表达的进一步处理)。

33.4.6.2. 组合类型

ECPG(Oracle Pro*c兼容)中并不直接支持组合类型,但是有一种可能的简单变通方案。可用的变通方案和上述用于数组的方案相似:要么单独访问每一个属性或者使用外部字符串表达。

对于下列例子,假定有下面的类型和表:

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

最显而易见的解决方案是单独访问每一个属性。下面的程序通过单独选择类型comp_t的每一个属性从例子表中检索数据:

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

/* 将组合类型列的每一个元素放在 SELECT 列表中。 */
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)
{
    /* 将组合类型列的每一个元素取到主变量中。 */
    EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

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

EXEC SQL CLOSE cur1;

为了加强这个例子,在FETCH命令中存储值的主变量可以被集中在一个结构中。结构形式的主变量的详情可见Section 33.4.5.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;

/* 将组合类型列的每一个元素放在 SELECT 列表中。 */
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)
{
    /* 将 SELECT 列表中的所有值放入一个结构。 */
    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)
{
    /* 将 SELECT 列表中的所有值放入一个结构。 */
    EXEC SQL FETCH FROM cur1 INTO :compval;

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

通过这种方法,即便 ECPG(Oracle Pro*c兼容)不理解组合类型本身,组合类型也能够几乎无缝地被映射到结构。

最后,也可以在类型char[]VARCHAR[]的主变量中把组合类型值存储成它们的外部字符串表达。但是如果使用那种方法,就不太可能从主程序中访问该值的各个域了。

33.4.6.3. 用户定义的基础类型

ECPG(Oracle Pro*c兼容)并不直接支持新的用户定义的基本类型。你可以使用外部字符串表达以及类型char[]VARCHAR[]的主变量,并且这种方案事实上对很多类型都是合适和足够的。

这里有一个使用来自Section 38.13中例子里的数据类型complex的例子。该类型的外部字符串表达是(%f,%f),它被定义在函数complex_in()以及Section 38.13中的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(Oracle Pro*c兼容)中直接使用用户定义的类型,而是创建一个在用户定义的类型和 ECPG(Oracle Pro*c兼容)能处理的简单类型之间转换的函数或者造型。不过要注意,在类型系统中引入类型造型(特别是隐式造型)要非常小心。

例如,

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)');

相同的效果。

33.4.7. 指示符

上述例子并没有处理空值。事实上,如果检索的例子从数据库取到了一个空值,它们将会产生一个错误。要能够向数据库传递空值或者从数据库检索空值,你需要对每一个包含数据的主变量追加一个次要主变量说明。这个次要主变量被称为指示符并且包含一个说明数据是否为空的标志,如果为空真正的主变量中的值就应该被忽略。这里有一个能正确处理检索空值的例子:

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;

如果值不为空,指示符变量val_ind将为零;否则它将为负值。

指示符有另一种功能:如果指示符值为正,它表示值不为空,但是当它被存储在主变量中时已被截断。

如果参数-r no_indicator被传递给预处理器ecpg(Oracle Pro*c兼容),它会工作在无指示符模式。在无指示符模式中,如果没有指定指示符变量,对于字符串类型空值被标志(在输入和输出上)为空串,对于整数类型空值被标志为类型的最低可能值(例如,int的是INT_MIN)。