紀(jì)洪波,崔立業(yè)
(通化師范學(xué)院 計算機科學(xué)系,吉林 通化 134002;2.吉電股份四平熱電分公司)
PostgreSQL[1]數(shù)據(jù)庫是非常流行的一款企業(yè)級開源數(shù)據(jù)庫,其歷史悠久、功能豐富,并且?guī)缀蹩梢宰龅脚cOracle數(shù)據(jù)庫的無縫兼容.PostgreSQL數(shù)據(jù)庫有很多為人稱道的特點,其中之一就是優(yōu)越的可編程性.PostgreSQL可以使用類似于Oracle PL/SQL的PL/pgSQL語言進(jìn)行存儲過程、觸發(fā)器的開發(fā),還提供了具有更強流程控制能力的PL/Perl、PL/Python、PL/Ruby等內(nèi)置腳本語言進(jìn)行開發(fā),甚至可以使用C、C++或者Java語言進(jìn)行開發(fā).
這其中SQL語言開發(fā)是擴(kuò)展數(shù)據(jù)庫的最基本工具,幾乎所有的數(shù)據(jù)庫都包含.SQL語言的優(yōu)點是面向集合、功能豐富、使用簡單,非常適合在數(shù)據(jù)庫中作數(shù)據(jù)處理.但是和其他高級語言相比SQL語言的缺點也是顯而易見的,交互性差、控制邏輯支持弱、靈活性不夠,特別是和操作系統(tǒng)的交互幾乎就是不可能的.所以PostgreSQL提供了對流程控制能力更強編程語言的支持,例如:Perl、PL/Python、PL/Ruby等.但是這些語言無論是運行效率還是與操作系統(tǒng)底層通訊的能力都還是有限的.不過PostgreSQL也想到了這一點,那就是使用C和C++語言來進(jìn)行功能擴(kuò)展.PostgreSQL數(shù)據(jù)庫提供了一套非常方便高效的使用C/C++擴(kuò)展開發(fā)方法,很多優(yōu)秀的擴(kuò)展都是使用C/C++編寫的,例如:PostGIS(地理信息數(shù)據(jù)擴(kuò)展)、TSearch或OpenFTS(全文檢索擴(kuò)展)、Slony-I(異步主/從復(fù)制方案)和XPath擴(kuò)展等.
PostgreSQL官方文檔對擴(kuò)展開發(fā)描述非常詳細(xì),不過唯獨沒有給出Windows平臺MSVC環(huán)境下的開發(fā)過程.按照其它平臺的描述和C語言標(biāo)準(zhǔn)推斷,使用MSVC開發(fā)擴(kuò)展需要嚴(yán)格按照標(biāo)準(zhǔn)C語言進(jìn)行開發(fā),包含頭文件(postgres.h、fmgr.h)和postgres.lib庫,使用特定的擴(kuò)展宏P(guān)G_MODULE_MAGIC和PG_FUNCTION_INFO_V1進(jìn)行開發(fā).然后將C源代碼編譯成動態(tài)鏈接庫(DLL),并將該庫放到PostgreSQL的搜索目錄中(也可以在安裝擴(kuò)展庫時指定全路徑),然后使用psql或者pgAdmin等工具運行CREATE FUNCTION SQL命令添加這個函數(shù)到數(shù)據(jù)庫中,授權(quán)的用戶就可以使用這些擴(kuò)展了.
但是在實施開發(fā)過程的時候會發(fā)現(xiàn),C源程序根本無法編譯,即使編譯也不能被PostgreSQL數(shù)據(jù)庫識別.這是由于PostgreSQL最初并不是為Windows開發(fā),也不是使用MSVC編譯的數(shù)據(jù)庫系統(tǒng).本文討論如何通過修改PostgreSQL的源代碼頭文件解決編譯問題,以及具體開發(fā)步驟和編程方法.
(1)設(shè)置開發(fā)環(huán)境.PostgreSQL數(shù)據(jù)庫最初是為Unix操作系統(tǒng)設(shè)計的,所以編程風(fēng)格和符號定義都不是非常符合Windows習(xí)慣.在Windows平臺上,可以使用GCC的一個移植版本MinGW編譯PostgreSQL擴(kuò)展,雖然這樣做比較容易,但編譯出來的程序體積一般比較大.本文選擇的9.0.1版本的PostgreSQL數(shù)據(jù)庫是由VC2008編譯完成的,所以本文討論的是使用VC2008編譯擴(kuò)展,這樣可以達(dá)到最好的兼容性.
首先修改Visual Studio的變量設(shè)置文件vsvars32.bat.添加PostgreSQL開發(fā)使用的頭文件和庫文件路徑到環(huán)境變量INCLUDE和LIB中,這樣VC編譯的時候才可以找到需要的文件.
(2)修改PostgreSQL頭文件.PostgreSQL自帶開發(fā)庫及頭文件,不過其自帶頭文件符號定義不是兼容MSVC的動態(tài)共享庫(動態(tài)鏈接庫)的接口標(biāo)準(zhǔn).編譯生成的動態(tài)共享庫PostgreSQL不能識別其中的功能函數(shù),在閱讀其大量源代碼之后,筆者發(fā)現(xiàn)解決這個問題的方法是對頭文件fmgr.h中的兩個宏定義進(jìn)行修改.
修改后這兩個宏P(guān)G_MODULE_MAGIC[2]和PG_FUNCTION_INFO_V1的原定義程序段如下:
#define PG_MODULE_MAGIC
extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void);
PGDLLEXPORT const Pg_magic_struct *
PG_MAGIC_FUNCTION_NAME(void)
{
static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA;
return &Pg_magic_data;
}
extern int no_such_variable
#define PG_FUNCTION_INFO_V1(funcname)
extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void);
PGDLLEXPORT const Pg_finfo_record *
CppConcat(pg_finfo_,funcname) (void)
{
static const Pg_finfo_record my_finfo = { 1 };
return &my_finfo;
}
extern int no_such_variable
主要的修改是:在這兩個宏定義中添加了PGDLLEXPORT宏(粗斜體)修飾,這個宏的定義如下:
#define PGDLLEXPORT __declspec (dllexport)
其主要作用是將動態(tài)共享庫中定義的方法暴露給外界調(diào)用代碼.這樣準(zhǔn)備工作就做完了.
(3)編寫擴(kuò)展C源文件.PostgreSQL手冊中包含了詳細(xì)的共享庫開發(fā)介紹,本文提供的程序就出自其中并作少許修改.源文件為c:→pgcex.c,源代碼如下:
#include
#include
#include
PG_MODULE_MAGIC;
/* 傳遞數(shù)值 */
PG_FUNCTION_INFO_V1(add_one);
PGDLLEXPORT Datum add_one(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
/* 傳遞引用,定長 */
PG_FUNCTION_INFO_V1(add_one_float8);
PGDLLEXPORT Datum add_one_float8(
PG_FUNCTION_ARGS)
{
/* 用于 FLOAT8 的宏,隱藏其傳遞引用的本質(zhì) */
float8 arg = PG_GETARG_FLOAT8(0);
PG_RETURN_FLOAT8(arg + 1.0);
}
PG_FUNCTION_INFO_V1(concat_text);
PGDLLEXPORT Datum concat_text(PG_FUNCTION_ARGS)
{
text *arg1 = PG_GETARG_TEXT_P(0);
text *arg2 = PG_GETARG_TEXT_P(1);
int32 new_text_size = VARSIZE(arg1)
+ VARSIZE(arg2) - VARHDRSZ;
text *new_text = (text *) palloc(
new_text_size);
SET_VARSIZE(new_text, new_text_size);
memcpy(VARDATA(new_text), VARDATA(arg1),
VARSIZE(arg1) - VARHDRSZ);
memcpy(VARDATA(new_text) +
(VARSIZE(arg1) - VARHDRSZ),
VARDATA(arg2),
VARSIZE(arg2) - VARHDRSZ);
PG_RETURN_TEXT_P(new_text);
}
(4)編譯C源代碼為擴(kuò)展庫.使用如下命令行編譯:
C:》cl pgcex.c -Fepgcex.dll -MD -LD /D “WIN32” /DLL postgres.lib
或
C:》cl /c pgcex.c /D “WIN32”
C:》link /DLL pgcex.obj postgres.lib
上述兩種方法都是正確的,第一種方法是直接將源代碼編譯成動態(tài)共享庫,并且動態(tài)共享庫的名字為pgcex.dll.-MD是使用MSVCRT.lib創(chuàng)建多線程DLL,-LD指定本次編譯任務(wù)是創(chuàng)建動態(tài)共享庫,postgres.lib是本擴(kuò)展開發(fā)必須鏈接的一個共享庫.
(1)安裝共享庫.打開pgAdmin3,執(zhí)行如下SQL代碼:
CREATE FUNCTION add_one(integer) RETURNS integer
AS 'c:/pgcex', 'add_one'
LANGUAGE C STRICT;
注意:重載了名字為 add_one()的SQL函數(shù)
CREATE FUNCTION add_one(double precision) RETURNS double precision
AS 'c:/pgcex', 'add_one_float8'
LANGUAGE C STRICT;
CREATE FUNCTION concat(text, text) RETURNS text
AS 'c:/pgcex', 'concat_text'
LANGUAGE C STRICT ;
在該SQL代碼中使用了全路徑'c:/pgcex'指定編譯的擴(kuò)展庫pgcex.dll的位置,這樣PostgreSQL可以找到并加載擴(kuò)展庫.當(dāng)然也可以將動態(tài)共享庫放到PostgreSQL的共享庫目錄中(通常為PostgreSQL安裝目錄中的lib子目錄),則在SQL語句中就可以直接使用pgcex而不需要指定路徑.
(2)執(zhí)行擴(kuò)展函數(shù).執(zhí)行擴(kuò)展函數(shù)使用如下SQL語句:
select add_one(3);
select add_one(5.0);
select concat('hello','world');
執(zhí)行結(jié)果如圖1.
(3)刪除共享函數(shù).在PostgreSQL數(shù)據(jù)庫中卸載擴(kuò)展函數(shù)使用如下
SQL語句
DROP FUNCTION add_one(integer);
DROP FUNCTION add_one(double precision);
DROP FUNCTION concat(text, text);
圖1 擴(kuò)展函數(shù)執(zhí)行結(jié)果圖
以上詳細(xì)討論了在Windows平臺上使用MSVC開發(fā)原生PostgreSQL擴(kuò)展庫的方法.實際工作當(dāng)中PostgreSQL通常會部署到Linux服務(wù)器上,擴(kuò)展的開發(fā)也相對容易.不過要是使其在Windows平臺也可用,那本文的內(nèi)容則是不能忽視的,希望能為各位同行提供一點兒幫助.
參考文獻(xiàn):
[1]Barry Stinson.PostgreSQL必備參考手冊[M].人民郵電出版社,2002.
[2]Dynamic C Language Functions-Section 35.9 of the postgreSQL 9.0.1 manual [R/OL].http://www.postgresql.org/docs/9.0/interactive/xfunc-c.html.