更新时间:2026-10-21 GMT+08:00
分享

使用非初级类型的宿主变量

在上述章节中,宿主变量均基于C语言的基础标量类型(如int、char[])。但在实际工程开发中,若频繁为单条记录的每个字段单独声明宿主变量,或为了读取多行数据而反复编写复杂的游标遍历代码,会导致程序极其冗长。

为此,ecpg支持使用C语言的非初级类型(复合与派生数据类型)作为宿主变量。主要包括:数组、结构体(struct)、typedef类型别名以及指针。

数组(Arrays)

数组作为宿主变量时,主要适用于以下两种场景:

  1. 在char[]或者VARCHAR[]中存储文本字符串。
  2. 无游标批量检索多行记录(Batch Select):若不使用数组,遍历多行查询结果必须依赖DECLARE CURSOR与FETCH循环;而利用数组作为宿主变量,可以一次性将多行结果直接提取到内存数组中,大幅简化代码逻辑。需要注意目标数组的内存容量必须足以容纳查询返回的所有行数,否则在数据写入时会触发严重的内存缓冲区溢出(Buffer Overflow)。

示例如下:

扫描pg_database系统表并且显示所有可用数据库的OID和名称。前提条件:需要创建testdb库。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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(dbname));
    memset(dbid, 0, sizeof(dbid));

    /* 连接数据库 */ 
    EXEC SQL CONNECT TO testdb; 

    /* 一次性将多行查询结果直接提取至数组宿主变量中 */ 
    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=

结构体(Structs)

结构体宿主变量允许将C语言结构体的成员变量与数据库查询列进行打包映射,实现“一次性提取整行多列数据”,极大地提高了代码的模块化程度。

以下示例表示从pg_database系统表以及使用pg_database_size()函数检索可用数据库的OID、名称和大小。在此示例中,按照结构体的成员名匹配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;

    /* 在达到结果集末尾时,跳出 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;

类型别名(typedef)

在ecpg中,引入类型别名有两种方式:

  • 标准C语言typedef:必须放置在BEGIN/END DECLARE SECTION声明段内部,以便ecpg预编译器能识别该类型:
    EXEC SQL BEGIN DECLARE SECTION;
    typedef char mychartype[40];
    typedef long serial_t;
    EXEC SQL END DECLARE SECTION;
  • ecpg专用类型映射指令EXEC SQL TYPE:建立别名到基础类型的映射。此指令无需写在声明段内:
    EXEC SQL TYPE serial_t IS long;

指针类型(Pointers)

ecpg支持声明常见基础类型的指针作为宿主变量,常用于动态内存分配或地址传递场景:
EXEC SQL BEGIN DECLARE SECTION;
int   *intp;
char **charp;
EXEC SQL END DECLARE SECTION;

相关文档