第五十二章. ecpg - 在 C 里嵌入 SQL
内容
为什么要嵌入 SQL?
概念
如何使用 ecpg
局限
从其他 RDBMS 移植
安装
寄语开发者
这里描写 Postgres 里在 C 软件包里嵌入 SQL.这部分是由 Linus Tolke (译注:是不是叫 Linus 的都是计算机天才?)和 Michael Meskes 写的.
注意:你可以象 PostgreSQL 其他部分那样拷贝和使用这些内容.
为什么要嵌入 SQL?
嵌入使用 SQL 比其他操作 SQL 查询的方法有一些小小的优势.它关心所有你的C 程序里面变量信息的往返.许多 RDBMS 软件包支持这种嵌入的语言.
有一个 ANSI 的标准描述嵌入的语言应该怎样工作.ecpg 被设计成尽可能地符合这个标准.因此这就有可能把为其他 RDBMS 软件包书写的嵌入式 SQL 程序移植到 Postgres 上来并因此而推动自由软件的精神的发展.
--------------------------------------------------------------------------------
概念
你在你的 C 程序里面用一些特殊的 SQL 东西来编写程序.对于定义可以在 SQL 语句里面使用的变量,你需要把它们放到一个特殊的定义段里面.你用一些特殊的语法来表达 SQL 查询.
在编译之前,你用嵌入的 SQLC 预编译器对你的文件进行预处理,由这个预编译器把你使用的 SQL 语句转换成把变量作为参数的函数调用.不管是作为输入到 SQL 语句里面的变量还是将包含返回结果的变量都被传到函数调用里.
然后你编译你的程序,在链接时,你的程序会与一个包含所用函数的特殊的库链接.这些函数(实际上大多是一个单一的函数)从参数里取得信息,用通常的方法(libpq)执行 SQL 查询并且把结果放回到声明为输出的参数里.
这样你运行你的程序时当控制到达 SQL 语句时,SQL 语句对数据库进行操作因而你可以对结果进行继续处理.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
如何使用 ecpg
本节讨论如何使用 ecpg 工具.
预编译器
预编译器叫 ecpg.在安装过后它存放在 Postgres bin/ 目录下面.
库
ecpg 库叫做 libecpg.a 或 libecpg.so.另外,该库用了 libpq 库与 Postgres 服务器通讯,所以你要将你的程序与这两个库链接: -lecpg -lpq.
库里面有一些方法是"隐藏"的,但是有时候这些方法可能提供非常有用的信息.
ECPGdebug(int on, FILE *stream) 如果第一个参数不为零则打开调试信息.调试信息记录在 stream. 大多数 SQL 语句把它的参数和结果记录日志.
最重要的一个 (ECPGdo)(函数)记录它的所有展开的字符串,也就是说,带有插入的所有变量的字符串和从Postgres 服务器来的结果.这个对搜索你的 SQL 语句的错误是非常有用的.
ECPGstatus() 这个方法/函数在我们与一个数据库联接后返回 TRUE 并且如果没有联接返回 FALSE .
错误控制
要想检测从 Postgres 服务器来得错误,你要包含如下一行
exec sql include sqlca;
到你的文件的包含段里.这样做将会定义一个结构和一个象下面一样名为 sqlca 的变量:
struct sqlca
{
char sqlcaid[8];
long sqlabc;
long sqlcode;
struct
{
int sqlerrml;
char sqlerrmc[70];
} sqlerrm;
char sqlerrp[8];
long sqlerrd[6];
/* 0: empty */
/* 1: OID of processed tuple if applicable */
/* 2: number of rows processed in an INSERT, UPDATE */
/* or DELETE statement */
/* 3: empty */
/* 4: empty */
/* 5: empty */
char sqlwarn[8];
/* 0: set to 'W' if at least one other is 'W' */
/* 1: if 'W' at least one character string */
/* value was truncated when it was */
/* stored into a host variable. */
/* 2: empty */
/* 3: empty */
/* 4: empty */
/* 5: empty */
/* 6: empty */
/* 7: empty */
char sqlext[8];
} sqlca;
如果最后一个SQL 语句发生了错误,那么 sqlca.sqlcode 将是非零值.如果 sqlca.sqlcode 小于 0 那么就是发生了某种严重的错误,象数据库定义与查询定义不一致等.如果大于 0 则是通常的错误,象表不包括所要求的行等.
sqlca.sqlerrm.sqlerrmc 将包含一个字符串描述该错误.该字符串以源文件的行号结尾。
可能发生的错误列表:
-12, Out of memory in line %d.
通常不出现这个错误。这是你的虚拟内存耗尽的标志。
-200, Unsupported type %s on line %d.
通常不出现这个错误.这表明预编译器生成了一些库(函数)不认得的东西.可能你运行的预编译器和当前库不兼容.
-201, Too many arguments line %d.
这意味着 Postgres 返回了比我们的匹配变量更多的参数.可能你漏了几个INTO :var1,:var2-列表里的宿主变量.
-202, Too few arguments line %d.
这意味着 Postgres 返回了比我们的对应宿主变量要少的参数.可能你多输入了几个INTO :var1,:var2-列表里的宿主变量.
-203, Too many matches line %d.
这意味着查询返回了多个行,但你声明的变量不是数组.你执行的 SELECT 可能不是唯一的.
-204, Not correctly formatted int type: %s line %d.
这意味着宿主变量是一个 int 类型并且 Postgres 数据库里的字段是另一种类型,包含着一个不能转换成一个 int 类型的数值.库(函数)使用 strtol 做此类转换.
-205, Not correctly formatted unsigned type: %s line %d.
这意味着宿主变量是一个 unsigned int(无符号整数)类型而Postgres 数据库里的字段是另外一种类型并且包含一个不能转换成unsigned int 的数值.库(函数)使用 strtoul 做这类转换.
-206, Not correctly formatted floating point type: %s line %d.
这意味着宿主变量是一个 float (浮点)类型而 Postgres 数据库里的字段是另外一种类型并且包含一个不能转换成float 的数值.库(函数)使用 strtod 做这类转换.
-207, Unable to convert %s to bool on line %d.
这意味着宿主变量是一个 bool (布尔)类型,而 Postgres 数据库里的字段值既不是 't' 也不是 'f'。
-208, Empty query line %d.
Postgres 返回 PGRES_EMPTY_QUERY,可能的原因是该查询实际上是空的。
-220, No such connection %s in line %d.
程序试图访问一个不存在的联接。
-221, Not connected in line %d.
程序试图访问一个存在的,但是没有打开的联接。
-230, Invalid statement name %s in line %d.
你试图使用的语句还没准备好。
-400, Postgres error: %s line %d.
某种 Postgres 错误。该消息包含来自 Postgres 后端的信息。
-401, Error in transaction processing line %d.
Postgres 给我们的信号,表明我们无法开始,提交或者回卷该事务。
-402, connect: could not open database %s.
与数据库的联接无法工作。
100, Data not found line %d.
这是一个"正常的"错误,告诉你你正在查询的东西找不到或者我们已经越过了游标的范围。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
局限
一些永远不会包括进来的东西以及用这个概念为什么或什么东西是没法做到的.
Oracle 的单任务能力 (single tasking possibility)
Oracle 在 AIX 3 上的版本 7.0 利用了 OS 支持的在共享内存段上的锁技术并且允许应用设计者用一种所谓的单任务方式链接一个应用.这时的体系结构就不是每个应用进程对应一个客户端进程,而是数据库部分和应用部分都在同一个进程上跑.在 oracle 的后期版本上这个特性不再被支持.
这需要对Postgres 的访问模式进行完全重新设计而且这些努力与获得的性能提高不相称.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
从其他 RDBMS 软件包移植
ecpg 的设计遵循 SQL 标准。所以从一个标准的 RDBMS 移植(应用)应该不是问题。糟糕的是现实世界里并没有所谓的标准的 RDBMS。所以 ecpg 同样试图去理解那些与标准不冲突的语法扩展。
下面的列表显示了所有以知的不兼容的地方。如果你发现一个没有列出来的不兼容点,请告之 Michael Meskes。不过要注意的是,我们只是列出那些其他 RDBMS 的预编译器和 ecpg 不兼容的东西,而没有列出ecpg 里有而其他 RDBMS 没有的特性。
FETCH 命令的语法
标准的 FETCH 命令的语法是:
FETCH [direction] [amount] IN|FROM cursor name.
不过,ORACLE 并不使用关键字 IN 和/或 FROM。我们没有办法增加这个特性,因为那样会导致分析冲突。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
安装
自版本 0.5 起 ecpg 就和 Postgres 一起发布.所以缺省安装时你就可以得到编译好并且安装好了的预编译器, 库和头文件.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
寄语开发者
本节是写给那些希望开发 ecpg 接口的人的.这里描述了这些接口是怎样工作的.本节的目的是给那些想认识一些内部机制的人提供一些信息,而"如何使用"那一章应该描述了所有通常的问题.所以,在深入 ecpg 内部之前请先读一下本节.如果你对 ecpg 如何工作不感兴趣,请略过本节.
ToDo 列表
这个版本的预编译器有一些缺陷:
库函数
to_date 等不存在。不过 Postgres 本身有一些很好的转换过程。所以你可能不会想要这些(函数)。
结构和联合
结构和联合必须在定义段里定义。
缺失的语句
下面的语句到目前为止还没有实现:
exec sql allocate
exec sql deallocate
SQLSTATE
信息 'no data found'
一个 exec sql insert select from 语句的 "no data" 错误信息应该是 100。
sqlwarn[6]
如果一个 SET DESCRIPTOR 语句里声明的 PRECISION 或 SCALE 值被忽略,sqlwarn[6] 应该是 'W'.
预编译器
首先写到输出的四行是 ecpg 的一贯做法.这些是两行注释和两行用于库接口必须的包含行.
然后预编译器对文件处理一遍,一边读输入文件,一边输出到输出文件.通常它只是把不加分析的把所有东西输出到输出文件里去.
当处理到 EXEC SQL 语句时,预编译器对之进行处理并根据语句做相应的改变.EXEC SQL 语句可以是下列之一:
定义段
定义段以
exec sql begin declare section;
开头,以
exec sql end declare section;
结束.在定义段里只允许变量定义.这个段里定义的每个变量同时也放到一个以变量名和对应类型为索引的变量列表里头.
特别是结构(struct)或者联合(union)的定义同样必须在定义段里面列出。否则ecpg 就不能处理这些类型,因为它不知道定义(是什么)。
定义同时也输出到文件里把这些变量作为通常的 C-变量.
特殊的类型 VARCHAR 和 VARCHAR2 的每个变量都被转换成一个命名结构.一个下面这样的定义:
VARCHAR var[180];
被转换成
struct varchar_var { int len; char arr[180]; } var;
包含语句
一个包含语句看起来象:
exec sql include filename;
注意这个与下面这行
#include
是不一样的。被包含的文件由 ecpg 本身分析。因此声明的头文件被包括在生成的 C 代码里。这样你也能够在一个头文件里声明 EXEC SQL 语句。
联接语句
一个联接语句看起来象:
exec sql connect to connection target;
它创建与指定数据库的联接。
connection target (联接目标)可以用下面的方法声明:
dbname[@server][:port][as connection name][user user name]
tcp:postgresql://server[:port][/dbname][as connection name][user user name]
unix:postgresql://server[:port][/dbname][as connection name][user user name]
character variable[as connection name][user user name]
character string[as connection name][user]
default
user
也有不同的方法声明用户名:
userid
userid/password
userid identified by password
userid using password
最后的 userid 和 password。每个都可以是一个文本常量,一个字符变量或者一个字符串。
断开语句
一个断开语句看起来象:
exec sql disconnect [connection target];
它关闭与指定数据库的联接。
connection target 可以用下面方法声明:
connection name
default
current
all
打开游标语句
一个打开游标语句看起来象:
exec sql open cursor;
它被忽略因而不拷贝到输出文件.
提交语句
一个提交语句看起来象
exec sql commit;
它被转换成输出
ECPGcommit(__LINE__);
回卷语句
一个回卷语句看起来象
exec sql rollback;
它被转换成如下输出
ECPGrollback(__LINE__);
其他语句
其他 SQL 语句是其他以 exec sql 开头并且以 ; 结尾的语句.所有两者之间的东西都被认为是一个 SQL 语句并做变量替换分析.
当一个符号以冒号(:)开头时,就会发生变量替换.然后就会到前面定义段里(生成)的变量列表里找出该名称的变量,然后根据该变量是用于输入还是输出,把指向该变量的指针写到输出里供函数访问使用.
对 SQL 请求里的每个变量,函数都得到另外十个参数:
作为特殊符号的类型。
指向数值或指针的指针。
如果变量是 varchar 或者 char,变量的尺寸。
数组里的元素个数(对数组抓取)。
数组里下一个元素的偏移量(对数组抓取)
做为特殊符号的标识器变量的类型。
一个指向标识器变量值或者标识器变量指针的指针。
0.
标识器数组里的元素个数(对数组抓取)。
标识器数组里下一个元素的偏移量(对数组抓取)。
一个完整的例子
下面是一个完整的描述预编译器对文件 foo.pgc 的输出的例子:
exec sql begin declare section;
int index;
int result;
exec sql end declare section;
...
exec sql select res into :result from mytable where index = :index;
被解释成:
/* Processed by ecpg (2.6.0) */
/* These two include files are added by the preprocessor */
#include ;
#include ;
/* exec sql begin declare section */
#line 1 "foo.pgc"
int index;
int result;
/* exec sql end declare section */
...
ECPGdo(__LINE__, NULL, "select res from mytable where index = ? ",
ECPGt_int,&(index),1L,1L,sizeof(int),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT,
ECPGt_int,&(result),1L,1L,sizeof(int),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
#line 147 "foo.pgc"
(本手册里的缩进是为可读性追加的,可不是预编译器能干的事.)
库
在库里面最重要的函数是 ECPGdo 函数.它有可变的参数.希望我们不会碰到那些对变参数个数的函数的参数个数有限制的机器.这些参数个数很容易多达 50 个.
这些参数是:
一个行号
这是一个只用于错误信息里的表明原始出错行的行号.
一个字符串
这是声明的 SQL 请求。这个请求将用输入变量修改,也就是说用那些编译时未知但要输入到请求里的变量修改.这里变量应该包含 “;” 放到字符串里.
输入变量
象预编译器节里描述的那样,每个输入变量换成十个参数.
ECPGt_EOIT
一个 enum (枚举)表明输入变量(列表)的结尾.
输出变量
象预编译器节里描述的那样,每个输入变量换成十个参数.这些变量将由函数填充.
ECPGt_EORT
一个 enum (枚举)表明变量(列表)的结尾.
所有 SQL 语句都在一次事务中执行,除非你进行了一次事务提交(commit).要获取这样的自动事务,第一个语句和/或事务提交或回卷后的第一个(语句)总是打开一个事务.要关闭这个缺省的特性,可以在命令行上使用 '-t' 选项。
待续:描述其他入口的位置.
-------------------------------------------------------------------------------