PostgreSQL C 語言函數(shù)

2021-09-03 14:22 更新
37.10.1. 動態(tài)載入
37.10.2. C 語言函數(shù)中的基本類型
37.10.3. 版本 1 的調(diào)用約定
37.10.4. 編寫代碼
37.10.5. 編譯和鏈接動態(tài)載入的函數(shù)
37.10.6. 組合類型參數(shù)
37.10.7. 返回行(組合類型)
37.10.8. 返回集合
37.10.9. 多態(tài)參數(shù)和返回類型
37.10.10. 共享內(nèi)存和 LWLock
37.10.11. 把 C++ 用于可擴展性

用戶定義的函數(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)用約定的支持。

37.10.1. 動態(tài)載入

在一個會話中第一次調(diào)用一個特定可載入對象文件中的用戶定義函數(shù)時, 動態(tài)載入器會把那個對象文件載入到內(nèi)存以便該函數(shù)被調(diào)用。因此用戶 定義的 C 函數(shù)的CREATE FUNCTION必須 為該函數(shù)指定兩塊信息:可載入對象文件的名稱,以及要在該對象文件中 調(diào)用的特定函數(shù)的 C 名稱(鏈接符號)。如果沒有顯式指定 C 名稱,則 它被假定為和 SQL 函數(shù)名相同。

下面的算法被用來基于CREATE FUNCTION 命令中給定的名稱來定位共享對象文件:

  1. 如果名稱是一個絕對路徑,則載入給定的文件。

  2. 如果該名稱以字符串$libdir開始,那么這一部分會被 PostgreSQL包的庫目錄名(在編譯時確定)替換。

  3. 如果該名稱不包含目錄部分,會在配置變量 dynamic_library_path指定的路徑中搜索該 文件。

  4. 否則(在該路徑中沒找到該文件,或者它包含一個非絕對目錄), 動態(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ā)生,但是未來可能會改變)。

37.10.2. C 語言函數(shù)中的基本類型

要了解如何編寫 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í)慣是intXX 表示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);
...

VARHDRSZsizeof(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ù)的例子了。

37.10.3. 版本 1 的調(diào)用約定

版本-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_xxx ()宏取得。(在非嚴格的函數(shù)中,需要使用PG_ARGISNULL()對參數(shù)是否為空提前做檢查;見下文。)結(jié)果要用對應(yīng)于返回類型的PG_RETURN_xxx ()宏返回。PG_GETARG_xxx ()的參數(shù)是要取得的函數(shù)參數(shù)的編號,從零開始計。PG_RETURN_xxx ()的參數(shù)是實際要返回的值。

這里是一些使用版本-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(n )允許一個函數(shù)測試是否每一個輸入為空(當(dāng)然,只需要在沒有聲明為strict的函數(shù)中這樣做)。和PG_GETARG_xxx ()宏一樣,輸入?yún)?shù)也是從零開始計數(shù)。注意應(yīng)該在驗證了一個參數(shù)不是空之后才執(zhí)行PG_GETARG_xxx ()。要返回一個空結(jié)果,應(yīng)執(zhí)行PG_RETURN_NULL(),它對嚴格的以及非嚴格的函數(shù)都有用。

乍一看,與使用普通的C調(diào)用約定相比,版本 1 編碼約定似乎只是毫無意義的愚民政策。 然而,它們確實允許我們處理NULLable 參數(shù)/返回值,以及toasted(壓縮或離線)值。

在版本-1接口中提供的其他選項是PG_GETARG_xxx ()宏的兩個變種。其中的第一種是PG_GETARG_xxx _COPY(),它確保返回的指定參數(shù)的拷貝可以被安全地寫入(通常的宏有時會返回一個指向表中物理存儲的值,它不能被寫入。使用PG_GETARG_xxx _COPY()宏可以保證得到一個可寫的結(jié)果)。第二種變種PG_GETARG_xxx _SLICE()宏有三個參數(shù)。第一個是函數(shù)參數(shù)的編號(如上文)。第二個和第三個是要被返回的段的偏移量和長度。偏移量從零開始計算,而負值的長度則表示要求返回該值的剩余部分。當(dāng)大型值的存儲類型為external時,這些宏提供了訪問這些大型值的更有效的方法(列的存儲類型可以使用ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype 來指定。storagetypeplainexternal、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。

37.10.4. 編寫代碼

在開始更高級的話題之前,我們應(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ù) mallocfree。 在每個事務(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.hpalloc.h

  • 對象文件中定義的符號名不能相互沖突或者與 PostgreSQL服務(wù)器可執(zhí)行程序中 定義的符號沖突。如果出現(xiàn)有關(guān)于此的錯誤消息,你將必須重命名你的 函數(shù)或者變量。

37.10.5. 編譯和鏈接動態(tài)載入的函數(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。一個共享庫能包含多于一個對象文件,但是我們在這里只使用一個。

FreeBSD

用來創(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 開始的版本。

HP-UX

該系統(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。

Linux

創(chuàng)建PIC的編譯器標(biāo)志是-fpic。創(chuàng)建一個共享庫的編譯器標(biāo)志是-shared。一個完整的例子看起來像:

cc -fPIC -c foo.c
cc -shared -o foo.so foo.o

macOS

這里是一個例子。它假定安裝了開發(fā)者工具。

cc -c foo.c
cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o

NetBSD

創(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

OpenBSD

創(chuàng)建PIC的編譯器標(biāo)志是-fPIC。ld -Bshareable被用來鏈接共享庫。

gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o

Solaris

創(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é)。

37.10.6. 組合類型參數(shù)

組合類型沒有像 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值,可以把它用合適的DatumGetXXX ()宏轉(zhuǎn)換成正確的數(shù)據(jù)類型。注意如果空值標(biāo)志被設(shè)置,那么返回值是沒有 意義的,所以在對結(jié)果做任何事情之前應(yīng)該先檢查空值標(biāo)志。

也有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。

37.10.7. 返回行(組合類型)

要從一個 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é)中會有一個例子。

37.10.8. 返回集合

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_xxx 宏完成),那么被反 TOAST 的拷貝將在每次循環(huán)中被釋放。相應(yīng)地, 如果你把這些值的引用保存在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類型。

37.10.9. 多態(tài)參數(shù)和返回類型

可以聲明 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)這種行為。

37.10.10. 共享內(nèi)存和 LWLock

外接程序可以在服務(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);
}

37.10.11. 把 C++ 用于可擴展性

盡管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)用棧泄露。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號