W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
用戶定義的函數(shù)可以用 C 編寫(或者可以與 C 兼容的語言,例如 C++)。 這類函數(shù)被編譯成動態(tài)載入對象(也被稱為共享庫)并且由服務(wù)器在 需要時載入。動態(tài)載入是把“C語言”函數(shù)和 “內(nèi)部”函數(shù)區(qū)分開的特性 — 兩者真正的編碼習(xí)慣 實際上是一樣的(因此,標(biāo)準的內(nèi)部函數(shù)庫是用戶定義的 C 函數(shù)很好 的源代碼實例)。
當(dāng)前僅有一種調(diào)用約定被用于C函數(shù)(“版本1”)。如下文所示,為函數(shù)編寫一個PG_FUNCTION_INFO_V1()
宏就能指示對該調(diào)用約定的支持。
在一個會話中第一次調(diào)用一個特定可載入對象文件中的用戶定義函數(shù)時, 動態(tài)載入器會把那個對象文件載入到內(nèi)存以便該函數(shù)被調(diào)用。因此用戶 定義的 C 函數(shù)的CREATE FUNCTION
必須 為該函數(shù)指定兩塊信息:可載入對象文件的名稱,以及要在該對象文件中 調(diào)用的特定函數(shù)的 C 名稱(鏈接符號)。如果沒有顯式指定 C 名稱,則 它被假定為和 SQL 函數(shù)名相同。
下面的算法被用來基于CREATE FUNCTION
命令中給定的名稱來定位共享對象文件:
如果名稱是一個絕對路徑,則載入給定的文件。
如果該名稱不包含目錄部分,會在配置變量 dynamic_library_path指定的路徑中搜索該 文件。
否則(在該路徑中沒找到該文件,或者它包含一個非絕對目錄), 動態(tài)載入器將嘗試接受給定的名稱,這大部分會導(dǎo)致失?。ㄒ蕾?當(dāng)前工作目錄是不可靠的)。
如果這個序列不起作用,會把平臺相關(guān)的共享庫文件名擴展(通常是 .so
)追加到給定的名稱并且再次嘗試上述 的過程。如果還是失敗,則載入失敗。
我們推薦相對于$libdir
或者通過動態(tài)庫路徑來 定位共享庫。如果升級版本時新的安裝在一個不同的位置,則可以 簡化升級過程。$libdir
實際表示的目錄可以用 命令pg_config --pkglibdir
來找到。
用于運行PostgreSQL服務(wù)器的 用戶 ID 必須能夠通過要載入文件的路徑。常見的錯誤是把文件或 更高層的目錄變得對postgres用戶 不可讀或者不可執(zhí)行。
在任何情況下,CREATE FUNCTION
命令 中給定的文件名會被原封不動地記錄在系統(tǒng)目錄中,這樣如果需要再次 載入該文件則會應(yīng)用同樣的過程。
PostgreSQL不會自動編譯 C 函數(shù)。在 從CREATE FUNCTION
命令中引用對象文件 之前,它必須先被編譯好。更多信息請見本文中的第 37.10.5 節(jié)。
為了確保動態(tài)載入對象文件不會被載入到一個不兼容的服務(wù)器, PostgreSQL會檢查該文件是否包含一個 帶有合適內(nèi)容的“magic block”。這允許服務(wù)器檢測到明顯的不兼 容,例如為不同PostgreSQL主版本編譯 的代碼。要包括一個 magic block,在寫上包括
頭文件fmgr.h
的語句之后,在該模塊的源文件之一(并且只 能在其中一個)中寫上這些:
PG_MODULE_MAGIC;
在被第一次使用后,動態(tài)載入對象文件會留在內(nèi)存中。在同一個會話中 對該函數(shù)未來的調(diào)用將只會消耗很小的負荷進行符號表查找。如果需要 重新載入一個對象文件(例如重新編譯以后),需要開始一個新的會話。
可以選擇讓一個動態(tài)載入文件包含初始化和終止化函數(shù)。如果文件包含一個 名為_PG_init
的函數(shù),則文件被載入后會立刻調(diào)用該函數(shù)。 該函數(shù)不接受參數(shù)并且應(yīng)該返回 void。如果文件包括一個名為 _PG_fini
的函數(shù),則在卸載該文件之前會立即調(diào)用該函數(shù)。 同樣地,該函數(shù)不接受參數(shù)并且應(yīng)該返回 void。注意將只在卸載文件的過程 中會調(diào)用_PG_fini
,進程結(jié)束時不會調(diào)用它(當(dāng)前,卸載被
禁用并且從不發(fā)生,但是未來可能會改變)。
要了解如何編寫 C 語言函數(shù),你需要了解 PostgreSQL如何在內(nèi)部表達基本數(shù)據(jù)類型 以及如何與函數(shù)傳遞它們。在內(nèi)部, PostgreSQL把一個基本類型認為是 “一團內(nèi)存”。在類型上定義的用戶定義函數(shù)說明了 PostgreSQL在該類型上操作的方式。也就 是說,PostgreSQL將只負責(zé)把數(shù)據(jù)存在磁盤以 及從磁盤檢索數(shù)據(jù),而使用你的用戶定義函數(shù)來輸入、處理和輸出該數(shù)據(jù)。
基本類型可以有三種內(nèi)部格式之一:
傳值,定長
傳引用,定長
串引用,變長
傳值類型在長度上只能是 1、2 或 4 字節(jié)(如果你的機器上 sizeof(Datum)
是 8,則還有 8 字節(jié))。你應(yīng)當(dāng)小心地 定義你的類型以便它們在所有的架構(gòu)上都是相同的尺寸(字節(jié))。例如, long
類型很危險,因為它在某些機器上是 4 字節(jié)但在 另外一些機器上是 8 字節(jié),而int
類型在大部分 Unix 機器 上都是 4 字節(jié)。在
Unix 機器上int4
類型一種合理的實現(xiàn) 可能是:
/* 4 字節(jié)整數(shù),傳值 */
typedef int int4;
(實際的 PostgreSQL C 代碼會把這種類型稱為int32
,因為 C 中的習(xí)慣是int
表示XX
XX
位。注意 因此還有尺寸為 1 字節(jié)的 C 類型int8
。SQL 類型 int8
在 C 中被稱為int64
。另見本文中的 表 37.2)。
在另一方面,任何尺寸的定長類型可以用傳引用的方法傳遞。例如,這里有一種 PostgreSQL類型的實現(xiàn)示例:
/* 16 字節(jié)結(jié)構(gòu),傳引用 */
typedef struct
{
double x, y;
} Point;
在PostgreSQL函數(shù)中傳進或傳出這種 類型時,只能使用指向這種類型的指針。要返回這樣一種類型的值,用 palloc
分配正確的內(nèi)存量,然后填充分配好的內(nèi)存, 并且返回一個指向該內(nèi)存的指針(還有,如果只想返回與具有相同數(shù)據(jù)類型的 一個輸入?yún)?shù)相同的值,可以跳過額外的palloc
并且返回 指向該輸入值的指針)。
最后,所有變長類型必須也以引用的方式傳遞。所有變長類型必須用一個 正好 4 字節(jié)的不透明長度域開始,該域會由SET_VARSIZE
設(shè)置,絕不要直接設(shè)置該域!所有要被存儲在該類型中的數(shù)據(jù)必須在內(nèi)存 中接著該長度域的后面存儲。長度域包含該結(jié)構(gòu)的總長度,也就是包括長 度域本身的尺寸。
另一個重點是要避免在數(shù)據(jù)類型值中留下未被初始化的位。例如,要注意 把可能存在于結(jié)構(gòu)中的任何對齊填充字節(jié)置零。如果不這樣做,你的數(shù)據(jù) 類型的邏輯等價常量可能會被規(guī)劃器認為是不等的,進而導(dǎo)致低效的(不過 還是正確的)計劃。
絕不要修改通過引用傳遞的輸入值的內(nèi)容。如果這樣做 很可能會破壞磁盤上的數(shù)據(jù),因為給出的指針可能直接指向一個磁盤緩沖 區(qū)。這條規(guī)則唯一的例外在第 37.12 節(jié)中有解釋。
例如,我們可以這樣定義類型text
:
typedef struct {
int32 length;
char data[FLEXIBLE_ARRAY_MEMBER];
} text;
[FLEXIBLE_ARRAY_MEMBER]
記號表示數(shù)據(jù)部分的實際 長度不由該聲明指定。
在操縱變長字節(jié)時,我們必須小心地分配正確數(shù)量的內(nèi)存并且正確地 設(shè)置長度域。例如,如果我們想在一個text
結(jié)構(gòu) 中存儲 40 字節(jié),我們可以使用這樣的代碼片段:
#include "postgres.h"
...
char buffer[40]; /* our source data */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
SET_VARSIZE(destination, VARHDRSZ + 40);
memcpy(destination->data, buffer, 40);
...
VARHDRSZ
和sizeof(int32)
一樣, 但是用宏VARHDRSZ
來引用變長類型的載荷的 尺寸被認為是比較好的風(fēng)格。還有,必須
使用SET_VARSIZE
宏來設(shè)置長度域,而不是用 簡單的賦值來設(shè)置。
本文中的表 37.2指定在編寫使用一種 PostgreSQL內(nèi)建類型的 C 語言函數(shù)時, 哪一種 C 類型對應(yīng)于哪一種 SQL 類型。 “定義文件”列給出了要得到該類型定義需要
包括的頭文件(實際的定義可能在一個由列舉文件包括的不同 文件中。推薦用戶堅持使用已定義的接口)。注意在任何源文 件中應(yīng)該總是首先包括postgres.h
, 因為它聲明了很多你需要的東西。
表 37.2. 內(nèi)建 SQL 類型等效的 C 類型
SQL 類型 | C 類型 | 定義文件 |
---|---|---|
boolean
|
bool
|
postgres.h (可能是編譯器內(nèi)建) |
box
|
BOX*
|
utils/geo_decls.h
|
bytea
|
bytea*
|
postgres.h
|
"char"
|
char
|
(編譯器內(nèi)建) |
character
|
BpChar*
|
postgres.h
|
cid
|
CommandId
|
postgres.h
|
date
|
DateADT
|
utils/date.h
|
smallint (int2 ) |
int16
|
postgres.h
|
int2vector
|
int2vector*
|
postgres.h
|
integer (int4 ) |
int32
|
postgres.h
|
real (float4 ) |
float4*
|
postgres.h
|
double precision (float8 ) |
float8*
|
postgres.h
|
interval
|
Interval*
|
datatype/timestamp.h
|
lseg
|
LSEG*
|
utils/geo_decls.h
|
name
|
Name
|
postgres.h
|
oid
|
oid
|
postgres.h
|
oidvector
|
oidvector*
|
postgres.h
|
path
|
PATH*
|
utils/geo_decls.h
|
point
|
POINT*
|
utils/geo_decls.h
|
regproc
|
regproc
|
postgres.h
|
text
|
text*
|
postgres.h
|
tid
|
ItemPointer
|
storage/itemptr.h
|
time
|
TimeADT
|
utils/date.h
|
time with time zone
|
TimeTzADT
|
utils/date.h
|
timestamp
|
Timestamp
|
datatype/timestamp.h
|
varchar
|
VarChar*
|
postgres.h
|
xid
|
TransactionId
|
postgres.h
|
現(xiàn)在我們已經(jīng)復(fù)習(xí)了基本類型所有可能的結(jié)構(gòu),現(xiàn)在可以展示一些 真實函數(shù)的例子了。
版本-1 的調(diào)用規(guī)范依賴于宏來降低傳參數(shù)和結(jié)果的復(fù)雜度。版本-1 函數(shù)的 C 聲明總是:
Datum funcname(PG_FUNCTION_ARGS)
此外,宏調(diào)用:
PG_FUNCTION_INFO_V1(funcname);
必須出現(xiàn)在同一個源文件中(按慣例會正好寫在該函數(shù)本身之前)。 這種宏調(diào)用不是internal
語言函數(shù)所需要的,因為 PostgreSQL會假定所有內(nèi)部函數(shù)都使用 版本-1 規(guī)范。不過,對于動態(tài)載入函數(shù)是必需的。
在版本-1 函數(shù)中,每一個實參都使用對應(yīng)于該參數(shù)數(shù)據(jù)類型的PG_GETARG_
宏取得。(在非嚴格的函數(shù)中,需要使用xxx
()PG_ARGISNULL()
對參數(shù)是否為空提前做檢查;見下文。)結(jié)果要用對應(yīng)于返回類型的PG_RETURN_
宏返回。xxx
()PG_GETARG_
的參數(shù)是要取得的函數(shù)參數(shù)的編號,從零開始計。xxx
()PG_RETURN_
的參數(shù)是實際要返回的值。xxx
()
這里是一些使用版本-1調(diào)用約定的例子:
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
PG_MODULE_MAGIC;
/* 傳值 */
PG_FUNCTION_INFO_V1(add_one);
Datum
add_one(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
/* 傳引用,定長 */
PG_FUNCTION_INFO_V1(add_one_float8);
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(makepoint);
Datum
makepoint(PG_FUNCTION_ARGS)
{
/* 這里,Point 的傳引用本質(zhì)沒有被掩蓋。 */
Point *pointx = PG_GETARG_POINT_P(0);
Point *pointy = PG_GETARG_POINT_P(1);
Point *new_point = (Point *) palloc(sizeof(Point));
new_point->x = pointx->x;
new_point->y = pointy->y;
PG_RETURN_POINT_P(new_point);
}
/* 傳引用,變長 */
PG_FUNCTION_INFO_V1(copytext);
Datum
copytext(PG_FUNCTION_ARGS)
{
text *t = PG_GETARG_TEXT_PP(0);
/*
* VARSIZE_ANY_EXHDR是該結(jié)構(gòu)的尺寸(以字節(jié)為單位)減去其頭部的
* VARHDRSZ或VARHDRSZ_SHORT。用一個完整長度的頭部構(gòu)建該拷貝。
*/
text *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
/*
* VARDATA是指向新結(jié)構(gòu)的數(shù)據(jù)區(qū)域的指針。來源可以是一個短數(shù)據(jù),
* 所以要通過VARDATA_ANY檢索它的數(shù)據(jù)。
*/
memcpy((void *) VARDATA(new_t), /* 目標(biāo) */
(void *) VARDATA_ANY(t), /* 源頭 */
VARSIZE_ANY_EXHDR(t)); /* 多少字節(jié) */
PG_RETURN_TEXT_P(new_t);
}
PG_FUNCTION_INFO_V1(concat_text);
Datum
concat_text(PG_FUNCTION_ARGS)
{
text *arg1 = PG_GETARG_TEXT_PP(0);
text *arg2 = PG_GETARG_TEXT_PP(1);
int32 arg1_size = VARSIZE_ANY_EXHDR(arg1);
int32 arg2_size = VARSIZE_ANY_EXHDR(arg2);
int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
text *new_text = (text *) palloc(new_text_size);
SET_VARSIZE(new_text, new_text_size);
memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size);
memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size);
PG_RETURN_TEXT_P(new_text);
}
假定上述代碼已經(jīng)準備在文件funcs.c
中并且被編譯成一個共享對象,我們可以用這樣的命令在PostgreSQL中定義函數(shù):
CREATE FUNCTION add_one(integer) RETURNS integer
AS 'DIRECTORY
/funcs', 'add_one'
LANGUAGE C STRICT;
-- 注意SQL函數(shù)名“add_one”的重載
CREATE FUNCTION add_one(double precision) RETURNS double precision
AS 'DIRECTORY
/funcs', 'add_one_float8'
LANGUAGE C STRICT;
CREATE FUNCTION makepoint(point, point) RETURNS point
AS 'DIRECTORY
/funcs', 'makepoint'
LANGUAGE C STRICT;
CREATE FUNCTION copytext(text) RETURNS text
AS 'DIRECTORY
/funcs', 'copytext'
LANGUAGE C STRICT;
CREATE FUNCTION concat_text(text, text) RETURNS text
AS 'DIRECTORY
/funcs', 'concat_text'
LANGUAGE C STRICT;
這里,DIRECTORY
表示共享庫文件的目錄(例如PostgreSQL的教程目錄,它包含這一節(jié)中用到的例子的代碼)。(更好的風(fēng)格是先把DIRECTORY
放入搜索路徑,在AS
子句中只使用'funcs'
。在任何情況下,我們可以為一個共享庫省略系統(tǒng)相關(guān)的擴展名,通常是
.so
)。
注意我們已經(jīng)把函數(shù)指定為“strict”,這意味著如果有任何輸入值為空,系統(tǒng)應(yīng)該自動假定得到空結(jié)果。通過這種做法,我們避免在函數(shù)代碼中檢查空值輸入。如果不這樣做,我們必須使用PG_ARGISNULL()
明確地檢查空值輸入。
宏PG_ARGISNULL(
允許一個函數(shù)測試是否每一個輸入為空(當(dāng)然,只需要在沒有聲明為“strict”的函數(shù)中這樣做)。和n
)PG_GETARG_
宏一樣,輸入?yún)?shù)也是從零開始計數(shù)。注意應(yīng)該在驗證了一個參數(shù)不是空之后才執(zhí)行xxx
()PG_GETARG_
。要返回一個空結(jié)果,應(yīng)執(zhí)行xxx
()PG_RETURN_NULL()
,它對嚴格的以及非嚴格的函數(shù)都有用。
乍一看,與使用普通的C
調(diào)用約定相比,版本 1 編碼約定似乎只是毫無意義的愚民政策。 然而,它們確實允許我們處理NULL
able 參數(shù)/返回值,以及“toasted”(壓縮或離線)值。
在版本-1接口中提供的其他選項是PG_GETARG_
宏的兩個變種。其中的第一種是xxx
()PG_GETARG_
,它確保返回的指定參數(shù)的拷貝可以被安全地寫入(通常的宏有時會返回一個指向表中物理存儲的值,它不能被寫入。使用xxx
_COPY()PG_GETARG_
宏可以保證得到一個可寫的結(jié)果)。第二種變種xxx
_COPY()PG_GETARG_
宏有三個參數(shù)。第一個是函數(shù)參數(shù)的編號(如上文)。第二個和第三個是要被返回的段的偏移量和長度。偏移量從零開始計算,而負值的長度則表示要求返回該值的剩余部分。當(dāng)大型值的存儲類型為“external”時,這些宏提供了訪問這些大型值的更有效的方法(列的存儲類型可以使用xxx
_SLICE()ALTER TABLE
來指定。tablename
ALTER COLUMN colname
SET STORAGE storagetype
storagetype
取plain
、external
、extended
或者main
)。
最后,版本-1 的函數(shù)調(diào)用規(guī)范可以返回集合結(jié)果(本文中的第 37.10.8 節(jié))、實現(xiàn)觸發(fā)器函數(shù)(第 38 章)和過程語言調(diào)用處理器(第 55 章)。更多細節(jié) 可見源代碼發(fā)布中的src/backend/utils/fmgr/README
。
在開始更高級的話題之前,我們應(yīng)該討論一下用于 PostgreSQL C 語言函數(shù)的編碼規(guī)則。 雖然可以把不是 C 編寫的函數(shù)載入到 PostgreSQL中,這通常是很困難的, 因為其他語言(例如 C++、FORTRAN 或者 Pascal)通常不會遵循和 C 相同的調(diào)用規(guī)范。也就是說,其他語言不會以同樣的方式在函數(shù)之間傳遞 參數(shù)以及返回值。由于這個原因,我們會假定你的 C 語言函數(shù)確實是用 C 編寫的。
編寫和編譯 C 函數(shù)的基本規(guī)則如下:
使用pg_config
--includedir-server
找出PostgreSQL服務(wù)器頭文件安裝在 系統(tǒng)的哪個位置。
編譯并且鏈接你的代碼(這樣它就能被動態(tài)載入到 PostgreSQL中)總是 要求特殊的標(biāo)志。對特定的操作系統(tǒng)的做法詳見 本文中的第 37.10.5 節(jié)。
記住為你的共享庫按本篇文章中的第 37.10.1 節(jié)中所述 定義一個“magic block”。
在分配內(nèi)存時,使用 PostgreSQL函數(shù) palloc
和 pfree
, 而不是使用對應(yīng)的 C 庫函數(shù) malloc
和free
。 在每個事務(wù)結(jié)束時會自動釋放通過palloc
分配的內(nèi)存,以免內(nèi)存泄露。
總是要使用memset
把你的結(jié)構(gòu)中的字節(jié)置零(或者 最開始就用palloc0
分配它們)。即使你對結(jié)構(gòu)中的 每個域都賦值,也可能有對齊填充(結(jié)構(gòu)中的空洞)包含著垃圾值。 如果不這樣做,很難支持哈希索引或哈希連接,因為你必須選出數(shù)據(jù) 結(jié)構(gòu)中有意義的位進行哈希計算。規(guī)劃器有時也依賴于用按位相等來 比較常量,因此如果邏輯等價的值不是按位相等的會導(dǎo)致出現(xiàn)不想要 的規(guī)劃結(jié)果。
大部分的內(nèi)部PostgreSQL類型 都聲明在postgres.h
中,不過函數(shù)管理器 接口(PG_FUNCTION_ARGS
等)在 fmgr.h
中,因此你將需要包括至少這兩個 文件。為了移植性,最好在包括任何其他系統(tǒng)或者用戶頭文件之前, 先包括
postgres.h
。包 括postgres.h
也將會為你包括 elog.h
和palloc.h
。
對象文件中定義的符號名不能相互沖突或者與 PostgreSQL服務(wù)器可執(zhí)行程序中 定義的符號沖突。如果出現(xiàn)有關(guān)于此的錯誤消息,你將必須重命名你的 函數(shù)或者變量。
在使用 C 編寫的PostgreSQL擴展函數(shù)之前,必須以一種特殊的方式編譯并且鏈接它們,以便產(chǎn)生一個能被服務(wù)器動態(tài)載入的文件。簡而言之,需要創(chuàng)建一個共享庫。
超出本節(jié)所含內(nèi)容之外的信息請參考你的操作系統(tǒng)文檔,特別是 C 編譯器(cc
)和鏈接編輯器(ld
)的手冊頁。另外,PostgreSQL源代碼的contrib
目錄中包含了一些可以工作的例子。但是,如果你依靠這些例子,也會使你的模塊依賴于PostgreSQL源碼的可用性。
創(chuàng)建共享庫通常與鏈接可執(zhí)行文件相似:首先源文件被編譯成對象文件,然后對象文件被鏈接起來。對象文件需要被創(chuàng)建為獨立位置代碼(PIC), ,這在概念上意味著當(dāng)它們被可執(zhí)行文件載入時,它們可以被放置在內(nèi)存中的任意位置(用于可執(zhí)行文件的對象文件通常不會以那種方式編譯)。鏈接一個共享庫的命令會包含特殊標(biāo)志來把它與鏈接一個可執(zhí)行文件區(qū)分開(至少在理論上 — 在某些系統(tǒng)上實際上很丑陋)。
在下列例子中,我們假定你的源代碼在一個文件foo.c
中,并且我們將創(chuàng)建一個共享庫foo.so
。除非特別注明,中間的對象文件將被稱為foo.o
。一個共享庫能包含多于一個對象文件,但是我們在這里只使用一個。
用來創(chuàng)建PIC的編譯器標(biāo)志是-fPIC
。要創(chuàng)建共享庫,編譯器標(biāo)志是-shared
。
gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o
這適用于FreeBSD從 3.0 開始的版本。
該系統(tǒng)編譯器創(chuàng)建PIC的編譯器標(biāo)志是+z
。當(dāng)使用GCC自己的-fPIC
時。用于共享庫的鏈接器標(biāo)志是-b
。因此:
cc +z -c foo.c
或者:
gcc -fPIC -c foo.c
并且然后:
ld -b -o foo.sl foo.o
和大部分其他系統(tǒng)不同,HP-UX為共享庫使用擴展.sl
。
創(chuàng)建PIC的編譯器標(biāo)志是-fpic
。創(chuàng)建一個共享庫的編譯器標(biāo)志是-shared
。一個完整的例子看起來像:
cc -fPIC -c foo.c
cc -shared -o foo.so foo.o
這里是一個例子。它假定安裝了開發(fā)者工具。
cc -c foo.c
cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
創(chuàng)建PIC的編譯器標(biāo)志是-fPIC
。對于ELF系統(tǒng),帶著標(biāo)志-shared
的編譯器被用來鏈接共享庫。在舊的非 ELF 系統(tǒng)上,ld -Bshareable
會被使用。
gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o
創(chuàng)建PIC的編譯器標(biāo)志是-fPIC
。ld -Bshareable
被用來鏈接共享庫。
gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o
創(chuàng)建PIC的編譯器標(biāo)志是-KPIC
(用于 Sun 編譯器)以及-fPIC
(用于GCC)。要鏈接共享庫,編譯器選項對幾種編譯器都是-G
或者是對GCC使用
-shared
。
cc -KPIC -c foo.c
cc -G -o foo.so foo.o
or
gcc -fPIC -c foo.c
gcc -G -o foo.so foo.o
如果這對你來說太復(fù)雜,你應(yīng)該考慮使用 GNU Libtool ,它會用一個統(tǒng)一的接口隱藏平臺差異。
結(jié)果的共享庫文件接著就可以被載入到PostgreSQL。當(dāng)把文件名指定給CREATE FUNCTION
命令時,必須把共享庫文件的名字給它,而不是中間的對象文件。注意系統(tǒng)的標(biāo)準共享庫擴展(通常是.so
或者.sl
)在CREATE FUNCTION
命令中可以被忽略,并且通常為了最好的可移植性應(yīng)該被忽略。
服務(wù)器會期望在哪里尋找共享庫文件請參考本文中第 37.10.1 節(jié)。
組合類型沒有像 C 結(jié)構(gòu)那樣的固定布局。組合類型的實例可能包含 空值域。此外,繼承層次中的組合類型可能具有和同一繼承層次中 其他成員不同的域。因此, PostgreSQL提供了函數(shù)接口 來訪問 C 的組合類型的域。
假設(shè)我們想要寫一個函數(shù)來回答查詢:
SELECT name, c_overpaid(emp, 1500) AS overpaid
FROM emp
WHERE name = 'Bill' OR name = 'Sam';
如果使用版本-1的調(diào)用規(guī)范,我們可以定義 c_overpaid
為:
#include "postgres.h"
#include "executor/executor.h" /* 用于 GetAttributeByName() */
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(c_overpaid);
Datum
c_overpaid(PG_FUNCTION_ARGS)
{
HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0);
int32 limit = PG_GETARG_INT32(1);
bool isnull;
Datum salary;
salary = GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
/* 另外,我們可能更想對空 salary 用 PG_RETURN_NULL() 。*/
PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}
GetAttributeByName
是返回指定行的屬性的 PostgreSQL系統(tǒng)函數(shù)。它有三個參數(shù): 類型為HeapTupleHeader
的傳入?yún)?shù)、想要訪問的函數(shù)名 以及一個說明該屬性是否為空的返回參數(shù)。 GetAttributeByName
返回一個
Datum
值,可以把它用合適的DatumGet
宏轉(zhuǎn)換成正確的數(shù)據(jù)類型。注意如果空值標(biāo)志被設(shè)置,那么返回值是沒有 意義的,所以在對結(jié)果做任何事情之前應(yīng)該先檢查空值標(biāo)志。XXX
()
也有GetAttributeByNum
函數(shù),它可以用目標(biāo)屬性 的屬性號而不是屬性名來選擇目標(biāo)屬性。
下面的命令聲明 SQL 中的c_overpaid
:
CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
AS 'DIRECTORY
/funcs', 'c_overpaid'
LANGUAGE C STRICT;
注意我們用了STRICT
,這樣我們不需要檢查輸入?yún)?shù)是否 為 NULL。
要從一個 C 語言函數(shù)中返回一個行或者組合類型值,你可以使用一種 特殊的 API,它提供的宏和函數(shù)隱藏了大部分的構(gòu)建組合數(shù)據(jù)類型的 復(fù)雜性。要使用這種 API,源文件中必須包括:
#include "funcapi.h"
有兩種方式可以構(gòu)建一個組合數(shù)據(jù)值(以后就叫一個“元組”): 可以從一個 Datum 值的數(shù)組構(gòu)造,或者從一個 C 字符串(可被傳遞給該元組 各列的數(shù)據(jù)類型的輸入轉(zhuǎn)換函數(shù))的數(shù)組構(gòu)造。在兩種情況下,都首先需要為 該元組的結(jié)構(gòu)獲得或者構(gòu)造一個TupleDesc
描述符。在處 理 Datum 時,需要把該TupleDesc
傳遞給
BlessTupleDesc
,接著為每一行調(diào)用 heap_form_tuple
。在處理 C 字符串時,需要把該 TupleDesc
傳遞給 TupleDescGetAttInMetadata
,接著為每一行調(diào)用 BuildTupleFromCStrings
。對于返回一個元組集合的函數(shù),
這些設(shè)置步驟可以在第一次調(diào)用該函數(shù)時一次性完成。
有一些助手函數(shù)可以用來設(shè)置所需的TupleDesc
。在大部分 返回組合值的函數(shù)中推薦的方式是調(diào)用:
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
傳遞傳給調(diào)用函數(shù)本身的同一個fcinfo
結(jié)構(gòu)(這當(dāng)然要求使用的 是版本-1 的調(diào)用規(guī)范)。resultTypeId
可以被指定為 NULL
或者一個本地變量的地址以接收該函數(shù)的結(jié)果類型 OID。 resultTupleDesc
應(yīng)該是一個本地 TupleDesc
變量的地址。檢查結(jié)果是不是
TYPEFUNC_COMPOSITE
,如果是則 resultTupleDesc
已經(jīng)被用所需的 TupleDesc
填充(如果不是,你可以報告一個錯誤,并且 返回“function returning record called in context that
cannot accept type record”字樣的消息)。
get_call_result_type
能夠解析一個多態(tài)函數(shù)結(jié)果的實際類型, 因此不僅在返回組合類型的函數(shù)中,在返回標(biāo)量多態(tài)結(jié)果的函數(shù)中它也是非常 有用的。resultTypeId
輸出主要用于返回多態(tài)標(biāo)量的函數(shù)。
get_call_result_type
有一個兄弟 get_expr_result_type
,它被用來解析被表示為一棵表達式 樹的函數(shù)調(diào)用的輸出類型。在嘗試確定來自函數(shù)外部的結(jié)果類型時可以用它。 也有一個get_func_result_type
,當(dāng)只有函數(shù)的 OID 可用時 可以用它。不過這些函數(shù)無法處理被聲明為返回
record
的 函數(shù),并且get_func_result_type
無法解析多態(tài)類型,因此你 應(yīng)該優(yōu)先使用get_call_result_type
。
比較老的,現(xiàn)在已經(jīng)被廢棄的獲得TupleDesc
的函數(shù)是:
TupleDesc RelationNameGetTupleDesc(const char *relname)
它可以為一個提到的關(guān)系的行類型得到TupleDesc
, 還有:
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
可以基于一個類型 OID 得到TupleDesc
。這可以被用來 為一種基礎(chǔ)或者組合類型獲得TupleDesc
。不過,對于 返回record
的函數(shù)它不起作用,并且它無法解析多態(tài)類型。
一旦有了一個TupleDesc
,如果計劃處理 Datum可以調(diào)用:
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
如果計劃處理 C 字符串,可調(diào)用:
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
如果正在編寫一個返回集合的函數(shù),你可以把這些函數(shù)的結(jié)果保存在 FuncCallContext
結(jié)構(gòu)中 — 分別使用 tuple_desc
或者attinmeta
域。
在處理 Datum 時,使用
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
來用 Datum 形式的用戶數(shù)據(jù)構(gòu)建一個HeapTuple
。
在處理 C 字符串時,使用
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
來用 C 字符串形式的用戶數(shù)據(jù)構(gòu)建一個HeapTuple
。 values
是一個 C 字符串?dāng)?shù)組,每一個元素是返回行 的一個屬性。每一個 C 字符串應(yīng)該是該屬性數(shù)據(jù)類型的輸入函數(shù)所期望 的格式。為了對一個屬性返回空值,values
數(shù)組中對 應(yīng)的指針應(yīng)該被設(shè)置為NULL
。對于你返回的每一行都將
再次調(diào)用這個函數(shù)。
一旦已經(jīng)構(gòu)建了一個要從函數(shù)中返回的元組,它必須被轉(zhuǎn)換成一個 Datum
。使用
HeapTupleGetDatum(HeapTuple tuple)
可把一個HeapTuple
轉(zhuǎn)換成合法的 Datum。如果你 只想返回一行,那么這個Datum
可以被直接返回,在一個 集合返回函數(shù)中它也可以被當(dāng)做當(dāng)前的返回值。
下一節(jié)中會有一個例子。
C 語言函數(shù)有兩個返回集合(多行)的選項。在一種稱為ValuePerCall 模式的方法中,一個集合返回函數(shù)被重復(fù)調(diào)用(每次傳遞相同的參數(shù)),并在每次調(diào)用時返回一個新行, 直到?jīng)]有更多行要返回并且 通過返回 NULL 來表示這一點。因此,集合返回函數(shù) (SRF) 必須在調(diào)用之間保存足夠的狀態(tài)以記住它在做什么并在每次調(diào)用時返回正確的下一項。 在另一種稱為Materialize模式的方法中,SRF 填充并返回一個包含其整個結(jié)果的 tuplestore 對象; 那么整個結(jié)果只發(fā)生一次調(diào)用,不需要調(diào)用間狀態(tài)。
使用 ValuePerCall 模式時,重要的是要記住查詢不能保證運行完成; 也就是說,由于諸如LIMIT
之類的選項, 執(zhí)行程序可能會在獲取所有行之前停止調(diào)用 set-returning 函數(shù)。 這意味著在最后一次調(diào)用中執(zhí)行清理活動是不安全的,因為這可能永遠不會發(fā)生。 對于需要訪問外部資源(例如文件描述符)的函數(shù),建議使用 Materialize 模式。
本節(jié)的其余部分記錄了一組使用 ValuePerCall 模式的 SRF 常用(盡管不是必須使用)的幫助程序宏。 有關(guān) Materialize 模式的其他詳細信息可以在src/backend/utils/fmgr/README
中找到。 此外,PostgreSQL源代碼分發(fā)中的contrib
模塊包含許多使用 ValuePerCall
和 Materialize 模式的 SRF 示例。
要使用此處描述的 ValuePerCall 支持宏,請包含funcapi.h
。 這些宏與結(jié)構(gòu)FuncCallContext
一起使用,該結(jié)構(gòu)包含需要跨調(diào)用保存的狀態(tài)。 在調(diào)用 SRF 中,fcinfo->flinfo->fn_extra
用于在調(diào)用之間保存 指向FuncCallContext
的指針。
宏在第一次使用時自動填充該字段,并期望在后續(xù)使用中找到相同的指針。
typedef struct FuncCallContext
{
/*
* 本次調(diào)用以前已經(jīng)被調(diào)用過多少次
*
* SRF_FIRSTCALL_INIT() 會為你把 call_cntr 初始化為 0,
* 并且在每次調(diào)用 SRF_RETURN_NEXT() 時增加。
*/
uint64 call_cntr;
/*
* 可選:最大調(diào)用次數(shù)
*
* 這里的 max_calls 只是為了方便,設(shè)置它是可選的。
* 如果沒有設(shè)置,你必須提供替代的方法來了解函數(shù)什么時候做完。
*/
uint64 max_calls;
/*
* 可選:指向用戶提供的上下文信息的指針
*
* user_fctx 是一個指向你自己的數(shù)據(jù)的指針,它可用來在函數(shù)的多次
* 調(diào)用之間保存任意的上下文信息。
*/
void *user_fctx;
/*
* 可選:指向包含屬性類型輸入元數(shù)據(jù)的結(jié)構(gòu)的指針
*
* attinmeta 被用在返回元組(即組合數(shù)據(jù)類型)時,在返回基本數(shù)據(jù)類型
* 時不會使用。只有想用BuildTupleFromCStrings()創(chuàng)建返回元組時才需要它。
*/
AttInMetadata *attinmeta;
/*
* 用于保存必須在多次調(diào)用間都存在的結(jié)構(gòu)的內(nèi)存上下文
*
* SRF_FIRSTCALL_INIT() 會為你設(shè)置 multi_call_memory_ctx,并且由
* SRF_RETURN_DONE() 來清理。對于任何需要在 SRF 的多次調(diào)用間都
* 存在的內(nèi)存來說,它是最合適的內(nèi)存上下文。
*/
MemoryContext multi_call_memory_ctx;
/*
* 可選:指向包含元組描述的結(jié)構(gòu)的指針
*
* tuple_desc 被用在返回元組(即組合數(shù)據(jù)類型)時,并且只有在用
* heap_form_tuple() 而不是 BuildTupleFromCStrings() 構(gòu)建元組時才需要它。
* 注意這里存儲的 TupleDesc 指針通常已經(jīng)被先運行過 BlessTupleDesc()。
*/
TupleDesc tuple_desc;
} FuncCallContext;
使用此基礎(chǔ)結(jié)構(gòu)的SRF將使用的宏是:
SRF_IS_FIRSTCALL()
來判斷你的函數(shù)是否是第一次被調(diào)用。在第一次調(diào)用時(只能在第一次調(diào)用時)使用:
SRF_FIRSTCALL_INIT()
使用它來確定您的函數(shù)是第一次還是隨后被調(diào)用。 在第一次調(diào)用時(僅),調(diào)用:
SRF_PERCALL_SETUP()
設(shè)置使用FuncCallContext
。
如果您的函數(shù)有數(shù)據(jù)要在當(dāng)前調(diào)用中返回,請使用:
SRF_RETURN_NEXT(funcctx, result)
把它返回給調(diào)用者(result
必須是類型Datum
, 可以是一個單一值或者按上文所述準備好的元組)。最后,當(dāng)函數(shù)完成了 數(shù)據(jù)返回后,可使用:
SRF_RETURN_DONE(funcctx)
來清理并且結(jié)束SRF。
SRF被調(diào)用時的當(dāng)前內(nèi)存上下文被稱作一個瞬時上下文, 在兩次調(diào)用之間會清除它。這意味著你不必對用palloc
分配的所有東西調(diào)用pfree
,它們將自動被釋放。不過, 如果你想要分配任何需要在多次調(diào)用間都存在的數(shù)據(jù)結(jié)構(gòu),需要把它們 放在其他地方。對于任何需要在SRF結(jié)束運行之前都存
在的數(shù)據(jù)來說,multi_call_memory_ctx
引用的內(nèi)存 上下文是一個合適的位置。在大多數(shù)情況下,這意味著您應(yīng)該在進行首次調(diào)用設(shè)置時切換到 multi_call_memory_ctx
。 使用funcctx->user_fctx
來保存指向任何此類交叉調(diào)用數(shù)據(jù)結(jié)構(gòu)的指針。 (您在multi_call_memory_ctx
中分配的數(shù)據(jù)將在查詢結(jié)束時自動消失,
因此也無需手動釋放該數(shù)據(jù)。)
雖然函數(shù)的實參在多次調(diào)用之間保持不變,但如果在瞬時上下文中 反 TOAST 了參數(shù)(通常由 PG_GETARG_
宏完成),那么被反 TOAST 的拷貝將在每次循環(huán)中被釋放。相應(yīng)地, 如果你把這些值的引用保存在xxx
user_fctx
中,你也必 須在反 TOAST 之后把它們拷貝到 multi_call_memory_ctx
中,或者確保你只在那個 上下文中反 TOAST 這些值。
一個完整的偽代碼例子:
Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
further declarations as needed
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* 這里是一次性設(shè)置代碼: */
user code
if returning composite
build TupleDesc, and perhaps AttInMetadata
endif returning composite
user code
MemoryContextSwitchTo(oldcontext);
}
/* 這里是每一次都要做的設(shè)置代碼: */
user code
funcctx = SRF_PERCALL_SETUP();
user code
/* 這里只是一種測試是否執(zhí)行完的方法: */
if (funcctx->call_cntr < funcctx->max_calls)
{
/* 這里返回另一個項: */
user code
obtain result Datum
SRF_RETURN_NEXT(funcctx, result);
}
else
{
/* 這里已經(jīng)完成了項的返回,所以只報告事實。 */
/* (不要將清理代碼放在這里的。) */
SRF_RETURN_DONE(funcctx);
}
}
一個返回組合類型的簡單SRF的完整例子:
PG_FUNCTION_INFO_V1(retcomposite);
Datum
retcomposite(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int call_cntr;
int max_calls;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
/* 只在第一次函數(shù)調(diào)用時做的事情 */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
/* 創(chuàng)建一個函數(shù)上下文,讓它在多次調(diào)用間都保持存在 */
funcctx = SRF_FIRSTCALL_INIT();
/* 切換到適合多次函數(shù)調(diào)用的內(nèi)存上下文 */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* 要返回的元組總數(shù) */
funcctx->max_calls = PG_GETARG_UINT32(0);
/* 為結(jié)果類型構(gòu)造一個元組描述符 */
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
/*
* 生成后面需要用來從原始 C 字符串產(chǎn)生元組的屬性元數(shù)據(jù)
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
/* 在每一次函數(shù)調(diào)用都要完成的事情 */
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta;
if (call_cntr < max_calls) /* 如果還有要發(fā)送的 */
{
char **values;
HeapTuple tuple;
Datum result;
/*
* 為構(gòu)建返回元組準備一個值數(shù)組。這應(yīng)該是一個 C
* 字符串?dāng)?shù)組,之后類型輸入函數(shù)會處理它。
*/
values = (char **) palloc(3 * sizeof(char *));
values[0] = (char *) palloc(16 * sizeof(char));
values[1] = (char *) palloc(16 * sizeof(char));
values[2] = (char *) palloc(16 * sizeof(char));
snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));
snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));
snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));
/* 構(gòu)建一個元組 */
tuple = BuildTupleFromCStrings(attinmeta, values);
/* 把元組變成 datum */
result = HeapTupleGetDatum(tuple);
/* 清理(實際并不必要) */
pfree(values[0]);
pfree(values[1]);
pfree(values[2]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
}
else /* 如果沒有要發(fā)送的 */
{
SRF_RETURN_DONE(funcctx);
}
}
在 SQL 中聲明這個函數(shù)的一種方法是:
CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);
CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
RETURNS SETOF __retcomposite
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
一種不同的方法是使用 OUT 參數(shù):
CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
OUT f1 integer, OUT f2 integer, OUT f3 integer)
RETURNS SETOF record
AS 'filename
', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
注意在這種方法中,函數(shù)的輸出類型在形式上是一種匿名的 record
類型。
可以聲明 C 語言函數(shù)來接受和返回第 37.2.5 節(jié)中描述的多態(tài)類型。當(dāng)函數(shù)參數(shù)或者返回 類型被定義為多態(tài)類型時,函數(shù)的編寫者無法提前知道會用什么數(shù)據(jù)類型 調(diào)用該函數(shù)或者該函數(shù)需要返回什么數(shù)據(jù)類型。在fmgr.h
中提供了兩種例程來允許版本-1
的 C 函數(shù)發(fā)現(xiàn)其參數(shù)的實際數(shù)據(jù)類型以及 它要返回的類型。這些例程被稱為 get_fn_expr_rettype(FmgrInfo *flinfo)
和 get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
。它們 返回結(jié)果或者參數(shù)的類型的 OID,或者當(dāng)該信息不可用時返回 InvalidOid
。結(jié)構(gòu)
flinfo
通常被當(dāng)做 fcinfo->flinfo
訪問。參數(shù)argnum
則是從零 開始計。get_call_result_type
也可被用作 get_fn_expr_rettype
的一種替代品。還有 get_fn_expr_variadic
,它可以被用來找出
variadic 參數(shù) 是否已經(jīng)被合并到了一個數(shù)組中。這主要用于 VARIADIC "any"
函數(shù),因為對于接收普通數(shù)組類型的 variadic 函數(shù)來說總是會發(fā)生這類合并。
例如,假設(shè)我們想要寫一個接收一個任意類型元素并且返回一個該類型的一維 數(shù)組的函數(shù):
PG_FUNCTION_INFO_V1(make_array);
Datum
make_array(PG_FUNCTION_ARGS)
{
ArrayType *result;
Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
Datum element;
bool isnull;
int16 typlen;
bool typbyval;
char typalign;
int ndims;
int dims[MAXDIM];
int lbs[MAXDIM];
if (!OidIsValid(element_type))
elog(ERROR, "could not determine data type of input");
/* 得到提供的元素,小心它為 NULL 的情況 */
isnull = PG_ARGISNULL(0);
if (isnull)
element = (Datum) 0;
else
element = PG_GETARG_DATUM(0);
/* 只有一個維度 */
ndims = 1;
/* 和一個元素 */
dims[0] = 1;
/* 且下界是 1 */
lbs[0] = 1;
/* 得到該元素類型所需的信息 */
get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);
/* 現(xiàn)在構(gòu)建數(shù)組 */
result = construct_md_array(&element, &isnull, ndims, dims, lbs,
element_type, typlen, typbyval, typalign);
PG_RETURN_ARRAYTYPE_P(result);
}
下面的命令在 SQL 中聲明函數(shù)make_array
:
CREATE FUNCTION make_array(anyelement) RETURNS anyarray
AS 'DIRECTORY
/funcs', 'make_array'
LANGUAGE C IMMUTABLE;
有一種只對 C 語言函數(shù)可用的多態(tài)變體:它們可以被聲明為接受類型為 "any"
的參數(shù)(注意這種類型名必須用雙引號引用,因為它也 是一個 SQL 保留字)。這和anyelement
相似,不過它不約束 不同的"any"
參數(shù)為同一種類型,它們也不會幫助確定函數(shù)的 結(jié)果類型。C 語言函數(shù)也能聲明它的第一個參數(shù)為 VARIADIC "any"
。這可以匹配一個或者多個任意類型的實參(
不需要是同一種類型)。這些參數(shù)不會像普通 variadic 函 數(shù)那樣被收集到一個數(shù)組中,它們將被單獨傳遞給該函數(shù)。使用這種特性時, 必須用PG_NARGS()
宏以及上述方法來判斷實參的個數(shù)和類 型。還有,這種函數(shù)的用戶可能希望在他們的函數(shù)調(diào)用中使用 VARIADIC
關(guān)鍵詞,以期讓該函數(shù)將數(shù)組元素作為單獨的參數(shù)
對待。如果想要這樣,在使用get_fn_expr_variadic
檢測被 標(biāo)記為VARIADIC
的實參之后,函數(shù)本身必須實現(xiàn)這種行為。
外接程序可以在服務(wù)器啟動時保留 LWLock 和共享內(nèi)存。 必須通過在shared_preload_libraries
中 指定外接程序的共享庫來預(yù)先載入它。從_PG_init
函數(shù)中調(diào)用
void RequestAddinShmemSpace(int size)
可以保留共享內(nèi)存。
通過從_PG_init
中調(diào)用
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
可以保留 LWLock。這將確保一個名為tranche_name
的 LWLock 數(shù)組可用,該數(shù)組的長度為num_lwlocks
。 使用GetNamedLWLockTranche
可得到該數(shù)組的指針。
為了避免可能的競爭情況,在連接并且初始化共享內(nèi)存時,每一個 后端應(yīng)該使用 LWLock AddinShmemInitLock
,如下所示:
static mystruct *ptr = NULL;
if (!ptr)
{
bool found;
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
ptr = ShmemInitStruct("my struct name", size, &found);
if (!found)
{
initialize contents of shmem area;
acquire any requested LWLocks using:
ptr->locks = GetNamedLWLockTranche("my tranche name");
}
LWLockRelease(AddinShmemInitLock);
}
盡管PostgreSQL后端是用 C 編寫的, 只要遵循下面的指導(dǎo)方針也可以用 C++ 編寫擴展:
所有被后端訪問的函數(shù)必須對后端呈現(xiàn)一種 C 接口,然后這些 C 函數(shù) 調(diào)用 C++ 函數(shù)。例如,對后端訪問的函數(shù)要求extern C
鏈接。對需要在后端和 C++ 代碼之間作為指針傳遞的任何函數(shù)也要 這樣做。
使用合適的釋放方法釋放內(nèi)存。例如,大部分后端內(nèi)存是通過 palloc()
分配的,所以應(yīng)使用pfree()
來釋放。在這種情況中使用 C++ 的delete
會失敗。
防止異常傳播到 C 代碼中(在所有extern C
函數(shù)的頂層 使用一個捕捉全部異常的塊)。即使 C++ 代碼不會顯式地拋出任何 異常也需要這樣做,因為類似內(nèi)存不足等事件仍會拋出異常。任何異常 都必須被捕捉并且用適當(dāng)?shù)腻e誤傳回給 C 接口。如果可能,用 -fno-exceptions
來編譯 C++ 以完全消滅異常。在這種 情況下,你必須在 C++ 代碼中檢查失敗,例如檢查new()
返回的
NULL。
如果從 C++ 代碼調(diào)用后端函數(shù),確定 C++ 調(diào)用棧值包含傳統(tǒng) C 風(fēng)格 的數(shù)據(jù)結(jié)構(gòu)(POD)。這是必要的,因為后端錯誤會 產(chǎn)生遠距離的longjmp()
,它無法正確的退回具有非 POD 對象的 C++ 調(diào)用棧。
總之,最好把 C++ 代碼放在與后端交互的extern C
函數(shù)之后, 并且避免異常、內(nèi)存和調(diào)用棧泄露。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: