第55章 手続き言語ハンドラの作成

現在のコンパイル言語用Version-1インタフェース以外のある言語で作成された関数の呼び出しはすべて、特定の言語用の呼び出しハンドラを経由します (これには、ユーザ定義手続き言語で作成された関数、SQLで作成された関数が含まれます)。 提供されたソーステキストを解釈するなどによって、関数の実行を意味のある方法で行うことは、呼び出しハンドラの責任です。 本章では、どのように新しい手続き言語の呼び出しハンドラを作成できるかについて概要を示します。

手続き言語用の呼び出しハンドラは通常の関数で、Cなどのコンパイル言語で作成し、Version-1インタフェースを使用し、引数を取らずにlanguage_handlerを返すものとしてPostgreSQLに登録しなければなりません。 この特殊な仮想型は、その関数を呼び出しハンドラとして識別し、SQLコマンド内で直接その関数が呼び出されることを防止します。 C言語の呼び出し規約と動的ロードについては37.10を参照してください。

呼び出しハンドラは、他の関数と同じ方法で呼び出されます。 引数値と呼び出された関数についての情報を含むFunctionCallInfoBaseData structのポインタを受け取り、Datum型の結果を返すもの(そして、SQLのNULLという結果を返そうとする場合に、FunctionCallInfoBaseData構造体のisnullフィールドを設定するかもしれないもの)と想定されています。 呼び出しハンドラと通常の呼び出される関数との違いは、FunctionCallInfoBaseData構造体のflinfo->fn_oidに、呼び出しハンドラ自身ではなく、実際に呼び出される関数のOIDが含まれるという点です。 呼び出しハンドラはこのフィールドを使用して、どの関数を呼び出すのかを決定しなければなりません。 また、渡された引数リストは、呼び出しハンドラの宣言ではなく、目的とする関数の宣言に従うよう設定されています。

pg_procシステムカタログから関数のエントリを取り出し、呼び出される関数の引数と戻り値の型を解析するまでを呼び出しハンドラが行います。 関数のCREATE FUNCTIONコマンドのAS句は、pg_procの行のprosrc列にあります。 これは通常、手続き言語自体で作成されたソーステキストですが、理論上はファイルへのパス名や、何らかの呼び出しハンドラに詳細に何をすべきかを通知するものとすることもできます。

1つのSQL文で同じ関数が何回も呼び出されることがよくあります。 呼び出しハンドラは、flinfo->fn_extraフィールドを使用して、呼び出す関数に関する情報を繰り返し検索することを防ぐことができます。 これは初期状態ではNULLですが、呼び出しハンドラによって呼び出す関数の情報を指すように設定することもできます。 その後の呼び出しでは、flinfo->fn_extraが非NULLであれば、それを使用して、情報検索の段階を省略することができます。 呼び出しハンドラはflinfo->fn_extraが少なくとも現在の問い合わせの終了まで有効なメモリを指しているかどうかを確認しなければなりません。 FmgrInfoデータ構造体は長く保持される可能性があるからです。 この方法の1つとして、flinfo->fn_mcxtで指定されたメモリコンテキスト内に余分なデータを割り当てることです。 このデータは通常FmgrInfo自身と同期間有効です。 しかし、ハンドラはまた、長時間メモリコンテキストにあるものを使用するかどうかを選ぶこともできます。 これにより関数定義情報を、問い合わせをまたいでキャッシュすることができます。

手続き言語関数がトリガとして呼び出された場合、引数は通常の方法では渡されず、FunctionCallInfoBaseDatacontextフィールドが、普通の関数呼び出しのようにNULLにはならずに、TriggerData構造体を指しています。 呼び出しハンドラは、手続き言語に対しトリガ情報を取り出す機構を提供しなければなりません。

以下は、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_MODULE_MAGIC;

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;
}

数千行のコードを上のドットの代わりに追加するだけで、呼び出しハンドラを完成することができます。

ハンドラ関数を動的ロード可能なモジュールにコンパイル(37.10.5を参照してください)した後、以下のコマンドでサンプルの手続き言語を登録することができます。

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

最低限の手続き言語を作成する場合には呼び出しハンドラを提供するだけで十分ですが、他にも省略可能ですが、その言語の利用をより簡便にするために提供できる2つの関数があります。 これらは有効性検証関数インラインハンドラです。 有効性検証関数を提供して、CREATE FUNCTION時に言語固有の検査を行うことができます。 インラインハンドラを提供して、言語にDOコマンド経由の匿名コードブロック実行をサポートさせることができます。

有効性検証関数が手続き言語により提供される場合、oid型の単一パラメータを取る関数として宣言しなければなりません。 有効性検証関数の結果は無視されます。 そのためよくvoidを返すものと宣言されます。 有効性検証関数はその手続き言語で関数を作成または置換するCREATE FUNCTIONの最後に呼び出されます。 渡されるOIDは関数のpg_proc行のOIDです。 有効性検証関数は通常の方法でこの行を取り出さなければならず、そして適切な検査を実行します。 まずユーザがCREATE FUNCTIONで到達できない有効性検証関数への明示的な呼び出しを診断するため、CheckFunctionValidatorAccess()を呼び出します。 典型的な検査として、さらに関数引数および結果の型がその言語でサポートされているかや関数本体がその言語において文法的に正しいかどうかを検証することが挙げられます。 有効性検証関数がその関数に問題がないことを判定したら、単に戻ります。 エラーがあることを判定したら、通常のereport()エラー報告機構を使用して報告しなければなりません。 エラーを返すことで、強制的にトランザクションはロールバックされ、不正な関数定義がコミットされることを防ぎます。

有効性検証関数は通常check_function_bodiesパラメータを遵守しなければなりません。 これが無効な場合、高価な、または文脈次第の検査を飛ばさなければなりません。 言語がコンパイル時のコード実行を提供するのであれば、有効性検証関数はそのような実行を引き起こす検査を抑制しなければなりません。 特にこのパラメータは、副作用や関数本体の他のデータベースオブジェクトへの依存を心配することなく手続き言語関数をロードできるように、pg_dumpにより無効にされます。 (この仕様のため呼び出しハンドラは有効性検証関数が完全にその関数を検査したことを前提としてはいけません。 有効性検証関数を持つ目的は、呼び出しハンドラが検査を省略できることではなく、明確なエラーがCREATE FUNCTIONコマンド内に存在する場合、それを即座にユーザに通知することです。) 厳密に何を検査すべきかの選択は主として有効性検査関数の裁量に委ねられていますが、check_function_bodiesが有効な場合にはCREATE FUNCTIONの中心となるコードは関数に関連づけられたSET句を実行するだけですので注意して下さい。 そのため、その結果がGUCパラメータの影響を受ける検査は、ダンプをリロードする時の偽の失敗を避けるために、check_function_bodiesが無効な場合には確実に飛ばさなければなりません。

インラインハンドラが手続き言語により提供される場合、その関数はinternal型の単一パラメータを取るものとして宣言されなければなりません。 インラインハンドラの結果は無視されます。 そのためよくvoidを返すものと宣言されます。 インラインハンドラは特定の手続き言語でDO文が実行された時に呼び出されます。 実際に渡されるパラメータはInlineCodeBlock構造体のポインタです。 ここにはDO文のパラメータ、具体的には実行される匿名コードブロックのテキスト、に関する情報が含まれています。 インラインハンドラはこのコードを実行し、戻らなければなりません。

簡単なCREATE EXTENSIONコマンドで言語をインストールすることが十分にできるように、これらの関数宣言とCREATE LANGUAGEコマンド自身を拡張としてまとめることを勧めます。 拡張の作成方法については37.17を参照してください。

独自の言語ハンドラを作成する際、標準配布物に含まれる手続き言語は優れたリファレンスです。 ソースツリーのsrc/plサブディレクトリを調べてください。 CREATE LANGUAGEマニュアルページもまた有用な情報を含みます。