处理非初级SQL数据类型
本节介绍如何处理ecpg应用中的非标量以及用户定义的SQL级别的数据类型。注意此处和使用非初级类型的宿主变量章节中介绍的对于非初级类型的宿主变量的处理不同。使用非初级类型的宿主变量章节侧重于在C语言侧声明结构体或数组来接收常规SQL列;而本节聚焦于当数据库表字段本身为SQL数组、组合类型或自定义类型时,ecpg在客户端的映射与处理规则。
SQL数组类型(Arrays)
ecpg不支持将多维或一维SQL数组字段直接整体映射到C语言的数组宿主变量上。在处理SQL数组列时,需遵循以下映射规则:
- 提取单元素:若查询语句仅访问SQL数组中的某个指定下标元素,可以直接使用与该元素类型相匹配的标量宿主变量(如int、char[]或VARCHAR[])进行接收。
- 提取多元素:若需将SQL数组的多个指定元素映射至C语言数组,必须在SELECT与INTO子句中逐个指定对应的数组下标元素。
- 强制约束(禁止整体映射):严禁直接将整列SQL数组映射给数组宿主变量。预编译器无法自动展开整个SQL数组结构,试图直接操作会导致语法解析或运行时错误。
示例场景1:提取提取SQL数组中的单个元素。
CREATE TABLE t3 (
ii integer[]
);
testdb=> SELECT * FROM t3;
ii
-------------
{1,2,3,4,5}
(1 row) 仅提取数组中的第4个元素,存储至标量宿主变量ii中:
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
示例场景2:逐元素提取并映射至C数组。
EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;
/* 在SELECT列表中显式展开下标,并在INTO中分别绑定C数组的具体下标 */
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 CLOSE cur1; 错误示例场景:整体映射SQL数组。
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;
...
} SQL组合类型(Composite Types)
ecpg无法直接原生支持SQL组合类型(例如:在结构体中声明成员变量为数据库date数据类型)。但是在实际开发中,可以通过单独访问组合类型中的每一个属性,或将其转换为外部字符串表达来处理组合类型数据。
示例的表结构与组合类型定义如下:
CREATE TYPE comp_t AS (intval integer, textval varchar(32)); CREATE TABLE t4 (compval comp_t); INSERT INTO t4 VALUES ( (256, 'GaussDB') );
方法1:单独检索属性并存入独立的标量宿主变量。
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; 方法2:单独检索属性并集中存入结构体宿主变量。
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; 方法3:使用通配符“*”简化属性展开。
EXEC SQL BEGIN DECLARE SECTION;
typedef struct
{
int intval;
varchar textval[33];
} comp_t;
comp_t compval;
EXEC SQL END DECLARE SECTION;
/* 使用(compval).*通配符自动展开组合类型的所有属性 */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
EXEC SQL OPEN cur1;
EXEC SQL WHENEVER NOT FOUND DO BREAK;
while (1)
{
/* 将展开后的所有属性批量提取至结构体变量中 */
EXEC SQL FETCH FROM cur1 INTO :compval;
printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}
EXEC SQL CLOSE cur1; 虽然ecpg无法解析组合类型本身,但是可以通过上述方法将组合类型映射到结构体中。
用户定义的基础类型
对于用户通过CREATE TYPE ... (INPUT = ..., OUTPUT = ...)自定义的扩展基础类型(如复数complex),ecpg无法预知其内部内存映射关系,因此无法将其直接绑定到任何特定的非字符类宿主变量。
对于自定义基础类型,必须通过其外部字符串格式(由其INPUT/OUTPUT函数定义的文本形式)进行交互,即统一使用char[]或VARCHAR[]类型的宿主变量接收和传入文本数据。
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)