第五十二章. ecpg - 在 C 里嵌入 SQL
內(nèi)容
為什么要嵌入 SQL?
概念
如何使用 ecpg
局限
從其他 RDBMS 移植
安裝
寄語開發(fā)者
這里描寫 Postgres 里在 C 軟件包里嵌入 SQL.這部分是由 Linus Tolke?。ㄗg注:是不是叫 Linus 的都是計算機天才?)和 Michael Meskes 寫的.
注意:你可以象 PostgreSQL 其他部分那樣拷貝和使用這些內(nèi)容.
為什么要嵌入 SQL?
嵌入使用 SQL 比其他操作 SQL 查詢的方法有一些小小的優(yōu)勢.它關(guān)心所有你的C 程序里面變量信息的往返.許多 RDBMS 軟件包支持這種嵌入的語言.
有一個 ANSI 的標準描述嵌入的語言應(yīng)該怎樣工作.ecpg 被設(shè)計成盡可能地符合這個標準.因此這就有可能把為其他 RDBMS 軟件包書寫的嵌入式 SQL 程序移植到 Postgres 上來并因此而推動自由軟件的精神的發(fā)展.
--------------------------------------------------------------------------------
概念
你在你的 C 程序里面用一些特殊的 SQL 東西來編寫程序.對于定義可以在 SQL 語句里面使用的變量,你需要把它們放到一個特殊的定義段里面.你用一些特殊的語法來表達 SQL 查詢.
在編譯之前,你用嵌入的 SQLC 預(yù)編譯器對你的文件進行預(yù)處理,由這個預(yù)編譯器把你使用的 SQL 語句轉(zhuǎn)換成把變量作為參數(shù)的函數(shù)調(diào)用.不管是作為輸入到 SQL 語句里面的變量還是將包含返回結(jié)果的變量都被傳到函數(shù)調(diào)用里.
然后你編譯你的程序,在鏈接時,你的程序會與一個包含所用函數(shù)的特殊的庫鏈接.這些函數(shù)(實際上大多是一個單一的函數(shù))從參數(shù)里取得信息,用通常的方法(libpq)執(zhí)行 SQL 查詢并且把結(jié)果放回到聲明為輸出的參數(shù)里.
這樣你運行你的程序時當控制到達 SQL 語句時,SQL 語句對數(shù)據(jù)庫進行操作因而你可以對結(jié)果進行繼續(xù)處理.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
如何使用 ecpg
本節(jié)討論如何使用 ecpg 工具.
預(yù)編譯器
預(yù)編譯器叫 ecpg.在安裝過后它存放在 Postgres bin/ 目錄下面.
庫
ecpg 庫叫做 libecpg.a 或 libecpg.so.另外,該庫用了 libpq 庫與 Postgres 服務(wù)器通訊,所以你要將你的程序與這兩個庫鏈接: -lecpg -lpq.
庫里面有一些方法是"隱藏"的,但是有時候這些方法可能提供非常有用的信息.
ECPGdebug(int on, FILE *stream) 如果第一個參數(shù)不為零則打開調(diào)試信息.調(diào)試信息記錄在 stream. 大多數(shù) SQL 語句把它的參數(shù)和結(jié)果記錄日志.
最重要的一個?。‥CPGdo)(函數(shù))記錄它的所有展開的字符串,也就是說,帶有插入的所有變量的字符串和從Postgres 服務(wù)器來的結(jié)果.這個對搜索你的 SQL 語句的錯誤是非常有用的.
ECPGstatus() 這個方法/函數(shù)在我們與一個數(shù)據(jù)庫聯(lián)接后返回 TRUE 并且如果沒有聯(lián)接返回 FALSE?。?
錯誤控制
要想檢測從 Postgres 服務(wù)器來得錯誤,你要包含如下一行
exec sql include sqlca;
到你的文件的包含段里.這樣做將會定義一個結(jié)構(gòu)和一個象下面一樣名為 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 語句發(fā)生了錯誤,那么 sqlca.sqlcode 將是非零值.如果 sqlca.sqlcode 小于 0 那么就是發(fā)生了某種嚴重的錯誤,象數(shù)據(jù)庫定義與查詢定義不一致等.如果大于 0 則是通常的錯誤,象表不包括所要求的行等.
sqlca.sqlerrm.sqlerrmc 將包含一個字符串描述該錯誤.該字符串以源文件的行號結(jié)尾。
可能發(fā)生的錯誤列表:
-12, Out of memory in line %d.
通常不出現(xiàn)這個錯誤。這是你的虛擬內(nèi)存耗盡的標志?!?
-200, Unsupported type %s on line %d.
通常不出現(xiàn)這個錯誤.這表明預(yù)編譯器生成了一些庫(函數(shù))不認得的東西.可能你運行的預(yù)編譯器和當前庫不兼容.
-201, Too many arguments line %d.
這意味著 Postgres 返回了比我們的匹配變量更多的參數(shù).可能你漏了幾個INTO :var1,:var2-列表里的宿主變量.
-202, Too few arguments line %d.
這意味著 Postgres 返回了比我們的對應(yīng)宿主變量要少的參數(shù).可能你多輸入了幾個INTO :var1,:var2-列表里的宿主變量.
-203, Too many matches line %d.
這意味著查詢返回了多個行,但你聲明的變量不是數(shù)組.你執(zhí)行的 SELECT 可能不是唯一的.
-204, Not correctly formatted int type: %s line %d.
這意味著宿主變量是一個 int 類型并且 Postgres 數(shù)據(jù)庫里的字段是另一種類型,包含著一個不能轉(zhuǎn)換成一個 int 類型的數(shù)值.庫(函數(shù))使用 strtol 做此類轉(zhuǎn)換.
-205, Not correctly formatted unsigned type: %s line %d.
這意味著宿主變量是一個 unsigned int(無符號整數(shù))類型而Postgres 數(shù)據(jù)庫里的字段是另外一種類型并且包含一個不能轉(zhuǎn)換成unsigned int 的數(shù)值.庫(函數(shù))使用 strtoul 做這類轉(zhuǎn)換.
-206, Not correctly formatted floating point type: %s line %d.
這意味著宿主變量是一個 float?。ǜ↑c)類型而 Postgres 數(shù)據(jù)庫里的字段是另外一種類型并且包含一個不能轉(zhuǎn)換成float 的數(shù)值.庫(函數(shù))使用 strtod 做這類轉(zhuǎn)換.
-207, Unable to convert %s to bool on line %d.
這意味著宿主變量是一個 bool?。ú紶枺╊愋?,而 Postgres 數(shù)據(jù)庫里的字段值既不是 't' 也不是 'f'?!?
-208, Empty query line %d.
Postgres 返回 PGRES_EMPTY_QUERY,可能的原因是該查詢實際上是空的?!?
-220, No such connection %s in line %d.
程序試圖訪問一個不存在的聯(lián)接。
-221, Not connected in line %d.
程序試圖訪問一個存在的,但是沒有打開的聯(lián)接?!?
-230, Invalid statement name %s in line %d.
你試圖使用的語句還沒準備好?!?
-400, Postgres error: %s line %d.
某種 Postgres 錯誤。該消息包含來自 Postgres 后端的信息。
-401, Error in transaction processing line %d.
Postgres 給我們的信號,表明我們無法開始,提交或者回卷該事務(wù)?!?
-402, connect: could not open database %s.
與數(shù)據(jù)庫的聯(lián)接無法工作?!?
100, Data not found line %d.
這是一個"正常的"錯誤,告訴你你正在查詢的東西找不到或者我們已經(jīng)越過了游標的范圍?!?
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
局限
一些永遠不會包括進來的東西以及用這個概念為什么或什么東西是沒法做到的.
Oracle 的單任務(wù)能力?。╯ingle tasking possibility)
Oracle 在 AIX 3 上的版本 7.0 利用了 OS 支持的在共享內(nèi)存段上的鎖技術(shù)并且允許應(yīng)用設(shè)計者用一種所謂的單任務(wù)方式鏈接一個應(yīng)用.這時的體系結(jié)構(gòu)就不是每個應(yīng)用進程對應(yīng)一個客戶端進程,而是數(shù)據(jù)庫部分和應(yīng)用部分都在同一個進程上跑.在 oracle 的后期版本上這個特性不再被支持.
這需要對Postgres 的訪問模式進行完全重新設(shè)計而且這些努力與獲得的性能提高不相稱.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
從其他 RDBMS 軟件包移植
ecpg 的設(shè)計遵循 SQL 標準。所以從一個標準的 RDBMS 移植(應(yīng)用)應(yīng)該不是問題。糟糕的是現(xiàn)實世界里并沒有所謂的標準的 RDBMS。所以 ecpg 同樣試圖去理解那些與標準不沖突的語法擴展?!?
下面的列表顯示了所有以知的不兼容的地方。如果你發(fā)現(xiàn)一個沒有列出來的不兼容點,請告之 Michael Meskes。不過要注意的是,我們只是列出那些其他 RDBMS 的預(yù)編譯器和 ecpg 不兼容的東西,而沒有列出ecpg 里有而其他 RDBMS 沒有的特性?!?
FETCH 命令的語法
標準的 FETCH 命令的語法是:
FETCH [direction] [amount] IN|FROM cursor name.
不過,ORACLE 并不使用關(guān)鍵字 IN 和/或 FROM。我們沒有辦法增加這個特性,因為那樣會導致分析沖突。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
安裝
自版本 0.5 起 ecpg 就和 Postgres 一起發(fā)布.所以缺省安裝時你就可以得到編譯好并且安裝好了的預(yù)編譯器, 庫和頭文件.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
寄語開發(fā)者
本節(jié)是寫給那些希望開發(fā) ecpg 接口的人的.這里描述了這些接口是怎樣工作的.本節(jié)的目的是給那些想認識一些內(nèi)部機制的人提供一些信息,而"如何使用"那一章應(yīng)該描述了所有通常的問題.所以,在深入 ecpg 內(nèi)部之前請先讀一下本節(jié).如果你對 ecpg 如何工作不感興趣,請略過本節(jié).
ToDo 列表
這個版本的預(yù)編譯器有一些缺陷:
庫函數(shù)
to_date 等不存在。不過 Postgres 本身有一些很好的轉(zhuǎn)換過程。所以你可能不會想要這些(函數(shù))。
結(jié)構(gòu)和聯(lián)合
結(jié)構(gòu)和聯(lián)合必須在定義段里定義?!?
缺失的語句
下面的語句到目前為止還沒有實現(xiàn):
exec sql allocate
exec sql deallocate
SQLSTATE
信息 'no data found'
一個 exec sql insert select from 語句的 "no data" 錯誤信息應(yīng)該是 100?!?
sqlwarn[6]
如果一個 SET DESCRIPTOR 語句里聲明的 PRECISION 或 SCALE 值被忽略,sqlwarn[6] 應(yīng)該是 'W'.
預(yù)編譯器
首先寫到輸出的四行是 ecpg 的一貫做法.這些是兩行注釋和兩行用于庫接口必須的包含行.
然后預(yù)編譯器對文件處理一遍,一邊讀輸入文件,一邊輸出到輸出文件.通常它只是把不加分析的把所有東西輸出到輸出文件里去.
當處理到 EXEC SQL 語句時,預(yù)編譯器對之進行處理并根據(jù)語句做相應(yīng)的改變.EXEC SQL 語句可以是下列之一:
定義段
定義段以
exec sql begin declare section;
開頭,以
exec sql end declare section;
結(jié)束.在定義段里只允許變量定義.這個段里定義的每個變量同時也放到一個以變量名和對應(yīng)類型為索引的變量列表里頭.
特別是結(jié)構(gòu)(struct)或者聯(lián)合(union)的定義同樣必須在定義段里面列出。否則ecpg 就不能處理這些類型,因為它不知道定義(是什么)?!?
定義同時也輸出到文件里把這些變量作為通常的 C-變量.
特殊的類型 VARCHAR 和 VARCHAR2 的每個變量都被轉(zhuǎn)換成一個命名結(jié)構(gòu).一個下面這樣的定義:
VARCHAR var[180];
被轉(zhuǎn)換成
struct varchar_var { int len; char arr[180]; } var;
包含語句
一個包含語句看起來象:
exec sql include filename;
注意這個與下面這行
#include
是不一樣的。被包含的文件由 ecpg 本身分析。因此聲明的頭文件被包括在生成的 C 代碼里。這樣你也能夠在一個頭文件里聲明 EXEC SQL 語句?!?
聯(lián)接語句
一個聯(lián)接語句看起來象:
exec sql connect to connection target;
它創(chuàng)建與指定數(shù)據(jù)庫的聯(lián)接。
connection target?。?lián)接目標)可以用下面的方法聲明:
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];
它關(guān)閉與指定數(shù)據(jù)庫的聯(lián)接?!?
connection target 可以用下面方法聲明:
connection name
default
current
all
打開游標語句
一個打開游標語句看起來象:
exec sql open cursor;
它被忽略因而不拷貝到輸出文件.
提交語句
一個提交語句看起來象
exec sql commit;
它被轉(zhuǎn)換成輸出
ECPGcommit(__LINE__);
回卷語句
一個回卷語句看起來象
exec sql rollback;
它被轉(zhuǎn)換成如下輸出
ECPGrollback(__LINE__);
其他語句
其他 SQL 語句是其他以 exec sql 開頭并且以 ; 結(jié)尾的語句.所有兩者之間的東西都被認為是一個 SQL 語句并做變量替換分析.
當一個符號以冒號(:)開頭時,就會發(fā)生變量替換.然后就會到前面定義段里(生成)的變量列表里找出該名稱的變量,然后根據(jù)該變量是用于輸入還是輸出,把指向該變量的指針寫到輸出里供函數(shù)訪問使用.
對 SQL 請求里的每個變量,函數(shù)都得到另外十個參數(shù):
作為特殊符號的類型?!?
指向數(shù)值或指針的指針?!?
如果變量是 varchar 或者 char,變量的尺寸?!?
數(shù)組里的元素個數(shù)(對數(shù)組抓取)?!?
數(shù)組里下一個元素的偏移量(對數(shù)組抓?。?
做為特殊符號的標識器變量的類型?!?
一個指向標識器變量值或者標識器變量指針的指針。
0.
標識器數(shù)組里的元素個數(shù)(對數(shù)組抓?。??!?
標識器數(shù)組里下一個元素的偏移量(對數(shù)組抓取)?!?
一個完整的例子
下面是一個完整的描述預(yù)編譯器對文件 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"
(本手冊里的縮進是為可讀性追加的,可不是預(yù)編譯器能干的事.)
庫
在庫里面最重要的函數(shù)是 ECPGdo 函數(shù).它有可變的參數(shù).希望我們不會碰到那些對變參數(shù)個數(shù)的函數(shù)的參數(shù)個數(shù)有限制的機器.這些參數(shù)個數(shù)很容易多達 50 個.
這些參數(shù)是:
一個行號
這是一個只用于錯誤信息里的表明原始出錯行的行號.
一個字符串
這是聲明的 SQL 請求。這個請求將用輸入變量修改,也就是說用那些編譯時未知但要輸入到請求里的變量修改.這里變量應(yīng)該包含 “;” 放到字符串里.
輸入變量
象預(yù)編譯器節(jié)里描述的那樣,每個輸入變量換成十個參數(shù).
ECPGt_EOIT
一個 enum (枚舉)表明輸入變量(列表)的結(jié)尾.
輸出變量
象預(yù)編譯器節(jié)里描述的那樣,每個輸入變量換成十個參數(shù).這些變量將由函數(shù)填充.
ECPGt_EORT
一個 enum (枚舉)表明變量(列表)的結(jié)尾.
所有 SQL 語句都在一次事務(wù)中執(zhí)行,除非你進行了一次事務(wù)提交(commit).要獲取這樣的自動事務(wù),第一個語句和/或事務(wù)提交或回卷后的第一個(語句)總是打開一個事務(wù).要關(guān)閉這個缺省的特性,可以在命令行上使用 '-t' 選項。
待續(xù):描述其他入口的位置.
-------------------------------------------------------------------------------