第五十二章. 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' 選項。
待續:描述其他入口的位置.
-------------------------------------------------------------------------------