Chapter 45. 書寫一個過程語言句柄

調用函數的時候,如果函數的書寫語言不是目前的"版本 1"的編譯語言接口(這包括用戶定義的過程語言寫的函數, 用 SQL 寫的函數,以及用版本 0 的編譯語言接口寫的函數),都會透過一個 調用句柄處理具體的語言。 調用句柄有責任用一種有意義的方法執行這個函數,比如說解析所提供的文本等等。本章簡介如何書寫一個新的過程語言調用句柄。

過程語言的調用句柄是一個"普通"的函數, 必須使用一種編譯語言來寫,比如 C,使用版本-1的接口,並且在 PostgreSQL 裡註冊成接受零個參數並且返回類型 language_handler。 這個特殊的偽類型標識該函數為一個調用句柄並且避免它直接在 SQL 命令中被調用。

調用句柄的調用方式和其它函數一樣:它接受一個指向一個 FunctionCallInfoData struct 的指針,這個指針包含參數值和有關被調用的函數的訊息, 並且預期它返回一個 Datum 結果(如果它希望返回一個 SQL 的空結果, 那麼可能設置 isnull 字串)。 調用句柄和普通的被調函數的區別是 FunctionCallInfoData 結構的 flinfo->fn_oid 字串強包含實際要調用的函數的 OID, 而不是調用句柄自身。調用句柄必須使用這個字串判斷要執行哪個函數。 通常,傳遞進來的參數列資料表也是按照目標函數的聲明設置的,而不是給調用句柄設置的。

從系統資料表 pg_proc 裡抓取函數入口以及分析被調函數的參數和返回類型就是調用句柄的事了。 來自 CREATE FUNCTION 命令中的 AS 子句將會在 pg_proc 行的 prosrc 字串中找到。這個通常是過程語言本身的源文本,但也可以是別的東西,比如一個指向某個文件的路徑名, 或者任何告訴調用句柄如何詳細處理的東西。

通常,每個 SQL 語句裡面可能要調用同一個函數多次。 調用句柄可以利用 flinfo->fn_extra 字串避免重複地查找有關被調函數地訊息。 這個字串初始為 NULL,但是可以被調用句柄設置為指向有關被調函數的訊息。 在隨後的調用中,如果 flinfo->fn_extra 已經為非NULL,那麼就可以直接使用它而免於重新查找訊息。 調用句柄必須確保 flinfo->fn_extra 是用於指向一塊至少會生存到目前查詢結束的內存區裡, 因為一個 FmgrInfo 資料結構將會保存那麼長的時間。 一個實現這個要求的方法是在 flinfo->fn_mcxt 聲明的內存環境裡分配一塊額外的資料; 這樣的資料通常和 FmgrInfo 自己有一樣的生命期。 但是句柄也可以同樣選擇使用一個更長生存期的環境,這樣它就可以跨查詢緩存函數定義。

在過程語言函數以觸發器的形式調用的時候,就沒有什麼參數以通常的方式傳遞進來, 而是 FunctionCallInfoDatacontext 字串指向一個 TriggerData 結構, 而不是像普通函數調用裡面的 NULL 那樣。一個語言句柄應該為過程語言函數提供獲取觸發器訊息的機制。

下面是一個用 C 寫的永遠過程語言句柄的模版:

#include "postgres.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "fmgr.h"
#include "access/heapam.h"
#include "utils/syscache.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"

PG_FUNCTION_INFO_V1(plsample_call_handler);

Datum
plsample_call_handler(PG_FUNCTION_ARGS)
{
    Datum          retval;

    if (CALLED_AS_TRIGGER(fcinfo))
    {
        /*
         * 以觸發器過程調用
         */
        TriggerData    *trigdata = (TriggerData *) fcinfo->context;

        retval = ...
    }
    else
    {
        /*
         * 以函數調用
         */

        retval = ...
    }

    return retval;
}

在打點的地方放上幾千行代碼就可以完成調用句柄。

在把句柄函數編譯成一個可裝載的模塊(參閱 Section 31.9.6)之後, 下面的命令就可以註冊這個例子過程語言:

CREATE FUNCTION plsample_call_handler() RETURNS language_handler
    AS 'filename'
    LANGUAGE C;
CREATE LANGUAGE plsample
    HANDLER plsample_call_handler;

如果想書寫自己的調用句柄,那麼包含在標準發佈裡面的過程語言是很好的例子。 參考一下原始碼樹中的 src/pl 子目錄。