25.2. 説明

25.2.1. Postgres の関数とTclプロシージャ名

Postgresでは、その関数の引数の数と引数の型が 異っていれば、同じ関数名を使用することが可能です。これはTclのプロシージャ名の つけ方と対立します。PL/Tclでも同じような柔軟性を持たせるために、 内部のTcl関数名には、pg_procプロシージャ行のオブジェクトIDが 関数名の一部として含まれています。したがって、同じ PostgresのTclにおいても、異った引数型を持つ関数は 異った関数と見なされます。

25.2.2. PL/Tcl関数定義

PL/Tclで関数を定義する際には、下記の標準構文を使用して下さい。

CREATE FUNCTION funcname (argument-types) RETURNS return-type AS '
    # PL/Tcl function body
' LANGUAGE 'pltcl';
     
関数が呼ばれた時、引数は変数$1 ... $nと いうように、Tclのプロシージャ本体に渡されます。結果はTclのコードから通常のように return文で返されます。例えば、2つのint4型の値で、 大きい方を返すものを定義するには、下記のようにします。
CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
    if {$1 > $2} {return $1}
    return $2
' LANGUAGE 'pltcl';
     
PL/Tcl関数からNULLを返す場合にはreturn_nullを 使用します。

複合型引数は、配列としてTclプロシージャに引き渡されます。配列の要素名は複合型の 属性名となります。実際の行で属性がNULLが入っていた場合には配列には 表示されません。下記はPL/Tclで書かれたoverpaid_2関数(支払われすぎ関数: この例は古いPostgresにも記載されています)の例です。

CREATE FUNCTION overpaid_2 (EMP) RETURNS bool AS '
    if {200000.0 < $1(salary)} {
        return "t"
    }
    if {$1(age) < 30 && 100000.0 < $1(salary)} {
        return "t"
    }
    return "f"
' LANGUAGE 'pltcl';
     

25.2.3. PL/Tclのグローバルデータ

特に後述するSPI関数を使用する場合などは、2つのプロシージャ間で保持される グローバルステータスデータを使用すると便利な場合があります。 1つのバックエンドから発行されたすべてのPL/Tclプロシージャは、 同じTclインタープリタを使用するので、容易に設定することができます。

PL/Tclプロシージャが予期しない作動に巻き込まれないようにするために、 upvarコマンドを使用することによって、各関数は配列を使用することが できます。この変数のグローバル名はプロシージャの内部名で、ローカル名はGDと なります。プロシージャのプライベートステータスデータではGDをご利用になることを お勧めします。複数のプロシージャで共用させる値の時のみ、 通常のTclのグローバル変数を使用して下さい。

25.2.4. PL/Tclのトリガプロシージャ

トリガ機能はPostgresで 引数のない、opaque型を返す関数と定義されています。PL/Tclでも同様です。

トリガプロシージャからの情報は下記の変数を使用して関数本体に引き渡されます。

$TG_name

CREATE TRIGGER文でのトリガ名

$TG_relid

トリガ機能を発行させた、テーブルのオブジェクトID。

$TG_relatts

前に空のリスト要素が付いた、テーブルのフィールド名のTclリスト。 したがって、lsearch Tclコマンドを使って要素名をリストから検索した場合には、 同じ正の数が返されます。それは、pg_attributeシステムカタログの フィールドが番号付けされているのと同様に1から始まります。

$TG_when

BEFORE、またはAFTERの文字列。トリガの呼び出しによって変動します。

$TG_level

ROW、またはSTATEMENTの文字列。トリガの呼び出しによって変動します。

$TG_op

INSERT、UPDATE、またはDELETEの文字列。トリガの呼び出しによって変動します。

$NEW

INSERT/UPDATEが行われたテーブルの行の新しい値、またはDELETEされた空の行の 時には空の配列。

$OLD

INSERT/UPDATEが行われたテーブルの行の古い値、またはINSERTされたの行の場合には 空の配列。

$GD

上記で説明のあったグローバルステータスデータの配列。

$args

CREATE TRIGGER文で指定された関数の引数のTclリスト。 引数は関数内では、$1 ... $nとして使用可能です。

トリガプロシージャからの返り値はOK、SKIPの文字列、またはTclコマンド'array get'で 返されたリストとなります。返り値がOKの場合は、このトリガを発行した通常の操作 (INSERT/UPDATE/DELETE)が処理を引き継ぎます。SKIPの場合は、 トリガ管理に作業を終了するように指示します。INSERT/UPDATEでの'array get'によって 返されたリストは、PL/Tclに対し、$NEWで指定されたものの代わりに挿入される、 変更された行をトリガマネージャーに返すように促します。言うまでもありませんが、 これらすべてはトリガがBEFORE、またはFOR EACH ROWで行われた時に 有効となります。

下記は、行に行われた更新階数をテーブル内の整数値に 保持させるトリガプロシージャの例です。 新しく行が挿入された場合、初期値は0となり、 更新が行われる度に1ずつ増えていきます。

CREATE FUNCTION trigfunc_modcount() RETURNS OPAQUE AS '
    switch $TG_op {
        INSERT {
            set NEW($1) 0
        }
        UPDATE {
            set NEW($1) $OLD($1)
            incr NEW($1)
        }
        default {
            return OK
        }
    }
    return [array get NEW]
' LANGUAGE 'pltcl';

CREATE TABLE mytab (num int4, modcnt int4, description text);

CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
    FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
     

25.2.5. PL/Tclからのデータベース接続

下記のコマンドは、PL/Tclプロシージャ内からデータベースへの接続を 行う時に使用できるコマンドです。

elog level msg

ログメッセージを発行します。Cのelog関数と同様にの 可能なレベルはNOTICE、ERROR、FATAL、DEBUG、NOINDです。

quote string

すべてのシングルクオートとバックスラッシュ文字に対して それらを2重にします。これは、spi_exec、または spi_prepare (spi_execpの値リストでは不可) に引き渡される問い合わせ文字列で変数が使用されている場合に使用します。 下記のような問い合わせ文字列、

"SELECT '$val' AS ret"
	
の場合で、Tcl変数valに実際は"doesn't"が入っている場合、 最終問い合わせ文字列は、
"SELECT 'doesn't' AS ret"
	
のようになり、spi_execspi_prepareを実行している際に エラーとなってしまいます。本来は
"SELECT 'doesn''t' AS ret"
	
のようになっているべきで、
"SELECT '[ quote $val ]' AS ret"
	
のように書かれているべきです。

spi_exec ?-count n? ?-array name? query ?loop-body?

問い合わせでパーザ(parser)/プランナ(planner)/オプティマイザ(optimizer)/ エクゼキュータ(executor)を呼びます。 オプション-countの値はspi_execに、 問い合わせで実行する最大行数を指定します。

問い合わせがSELECT文で、loop-bodyオプション(foreach文のような、 Tclコマンドの本体)が与えられていた場合、選択された各行を一つ一つ 評価し、continue/breakで期待されているように振舞います。 選択されたフィールドの値は、列名として変数に格納されます。 したがって

spi_exec "SELECT count(*) AS cnt FROM pg_proc"
	
は、変数$cntにpg_procシステムカタログにある行数を格納します。 -arrayオプションが与えられた場合には、列名は'name'という連想した 配列に格納され、各変数ではなく、列名がインデックスに使用されます。
spi_exec -array C "SELECT * FROM pg_class" {
    elog DEBUG "have table $C(relname)"
}
	
は、各pg_class行のDEBUGログメッセージを表示させます。 spi_execの返り値はグローバル変数 SPI_processedにあるような、問い合わせによって変更する行の数です。

spi_prepare query typelist

後の実行のためにAND SAVES問い合わせ計画を用意して下さい。計画が自動的に メモリコンテキストのトップレベルにコピーされる点で、 CレベルのSPI_prepareとは少々異なります。したがって、 現時点では保持せずに計画を用意することはできません。

問い合わせが引数を参照する場合、必ず型名はTclリストとして 渡される必要があります。spi_prepareは問い合わせIDを返し、 それはspi_execpへと続く、後の問い合わせで使用されます。 例としてspi_execpを参照して下さい。

spi_exec ?-count n? ?-arrayname? ?-nullsstring? queryid ?value-list? ?loop-body?

spi_prepareによって用意された計画を変数に置き換えて実行します。 オプション-countの値は、spi_execpに対して、 問い合わせで実行する最大行数を指定します。

オプション-nullsの値は、スペースと'n'文字で構成された文字列で、 spi_execpに、どの値がNULLであるかを指定します。与えられた場合は、 値の数と一致する数である必要があります。

queryidとは、spi_prepare呼び出しによって返されたIDです。

spi_prepareに対して型リストが渡された場合、問い合わせの後に、 Tclリストの値と一致する長さのものをspi_execpに渡す必要があります。 spi_prepareの型リストが空だった場合、この引数は省略する必要があります。

問い合わせがSELECT文であった場合、loop-bodyと選択されたフィールドの変数に ついては、spi_exec同じ現象が起こります。

下記は、PL/Tcl関数が用意された計画を使用する例です。

CREATE FUNCTION t1_count(int4, int4) RETURNS int4 AS '
    if {![ info exists GD(plan) ]} {
        # prepare the saved plan on the first call
        set GD(plan) [ spi_prepare \\
                "SELECT count(*) AS cnt FROM t1 WHERE num >= \\$1 AND num <= \\$2" \\
                int4 ]
    }
    spi_execp -count 1 $GD(plan) [ list $1 $2 ]
    return $cnt
' LANGUAGE 'pltcl';
	
Tclが認識すべき各バックスラッシュは、CREATE FUNCTIONでも バックスラッシュを生成するため、関数を作成している問い合わせでは 2重に表記する必要があります。spi_prepareに引き渡される 問い合わせ文字列の中では、引数の箇所を印すためと、$1に 最初の関数の呼び出しで与えられた値で置き換わらないようにするために ドル記号を記述するべきです。

モジュールと不明コマンド

PL/Tclには、使用頻度の高いものに対して特別なサポートが用意されています。 それは、pltcl_modulesとpltcl_modfuncsという、2つの特別な「魔法」のテーブルを 認識します。これらが存在する場合、'不明'モジュールは、作成された直後に インタプリタに組み込まれます。不明なTclプロシージャが呼び出された場合にはいつでも、 そのプロシージャがモジュールの中で定義されているかの確認を不明procにさせます。 その結果が真であれば、モジュールは要求通りに組み込まれます。この動作を 実行するには、-DPLTCL_UNKNOWN_SUPPORTセットと一緒にPL/Tclコールハンドラが コンパイルされている必要があります。

これらのテーブルを管理するサポートスクリプトは、 始めからインストールされている必要のある不明な モジュールのソースと一緒に、PL/Tclソースのmodulesサブディレクトリに あります。