PL/Tcl

PL/Tcl は Postgres データベースシステム 用の読み込み可能な手続き言語であり、Tcl 言語を使った関数とトリガ プロシージャを作成できます。

このパッケージはJan Wieck氏によって記述されたものを元にしています。

概要

PL/Tcl は、いくつかの制限を除き、C 言語で記述された関数が持つ機能 のほとんどを提供します。

全てが安全な Tcl インタプリタで実行されることは、良い制約になって います。安全な Tcl の制限付きのコマンドセットに加えて、SPI 経由で のデータベースへのアクセス、elog() 経由でのメッセージの出力を行な うための少ないコマンドも使用できます。データベースバックエンドの内 部へのアクセス、または、C のように Postgres ユーザ ID の権利を元にした OS レベルのアクセス手段の入手を行なうことはできません。従って、特権を 持たないデータベースユーザもこの言語を使うことができます。

この他の、内部的に付与される制限は、Tcl プロシージャは新しいデータ 型用の入出力関数を作成するために使うことができないということです。

インストール処理における設定段階にて Tcl/Tk サポートを指定した場合、 PL/Tcl 呼び出しハンドラ用の共有オブジェクトは自動的に構築され Postgres ライブラリディレクトリにインスト ールされます。

説明

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

Postgres では、異なる引数数または異なる 引数型を持つ限り、同一名の関数を異なる関数として使用できます。これ は Tcl プロシージャ名と相反します。PL/Tcl で同じ柔軟性を提供するた めに、内部的な Tcl プロシージャ名はそのプロシージャに対応する pg_proc の行のオブジェクト ID をその名前の一部に含んでいます。従っ て、同じ Postgres 関数の異なる引数型を持 つバージョンは Tcl でも異なるものになります。

PL/Tcl における関数の定義

PL/Tcl言語で関数を定義するには、次のような既知の文法を使います。

    CREATE FUNCTION funcname (argument-types) RETURNS returntype AS '
        # PL/Tcl function body
    ' LANGUAGE 'pltcl';
    
問い合わせ内でこの関数を呼ぶ時、引数は $1 ... $n という変数として、 Tcl プロシージャの本体に与えられます。ですので、2 つの int4 値の うちどちらが大きいかを返す、小さな最大値算出関数は次のように作成 されます。
    CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
        if {$1 > $2} {return $1}
	return $2
    ' LANGUAGE 'pltcl';
    
複合型引数は Tcl の配列として、プロシージャに与えられます。配列の 要素名は複合型の属性名になります。実際の行のある属性が NULL 値であ った場合、その配列には現れません!(古い Postgres 文書にあった)overpaid_2 関数を PL/Tcl で定義した例をここに示します。
    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';
    

PL/Tcl におけるグローバルデータ

あるプロシージャを呼び出す 2 つの間で保持されるグローバルな状態を 示すデータを幾つか持つことが有益な時(特に、後述の SPI 関数を使用 した時)があります。全ての PL/Tcl プロシージャは、1 つのバックエン ドが同じ安全な Tcl インタプリタを共有して実行されます。PL/Tcl プロ シージャを外部から保護することを補助するために、upvar コマンド経由 で各プロシージャが使用できる配列が作成されます。この変数のグローバ ルな名前はそのプロシージャの内部的な名前であり、ローカルな名前は GD です。

PL/Tcl におけるトリガプロシージャ

トリガプロシージャは Postgres 内で、 引数の無い、opaque 型を返す関数として定義されます。PL/Tcl 言語の そういった関数もトリガプロシージャとなります。

トリガマネージャからの情報は次の変数によって、プロシージャ本体に 与えられます。

$TG_name

CREATE TRIGGER 文でのトリガの名前

$TG_relid

トリガプロシージャの呼び出し元となるテーブルのオブジェクト ID。

$TG_relatts

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

$TG_when

トリガ呼び出しのイベントに依存した BEFORE または AFTER とい う文字列。

$TG_level

トリガ呼び出しのイベントに依存した ROW または STATEMENT とい う文字列。

$TG_op

トリガ呼び出しのイベントに依存した INSERT、UPDATE または DELETE という文字列。

$NEW

INSERT/UPDATE 動作時のテーブルの新しい行の値を持つ配列。DELETE の 場合は空となります。

$OLD

UPDATE/DELETE 動作時のテーブルの古い行の値をもつ配列。INSERT の場 合は空になります。

$GD

上述のグローバルな状態を示すデータ配列。

$args

CREATE TRIGGER 文で指定されたプロシージャの引数の Tcl リスト。引数 はプロシージャ本体で $1 ... $n としても参照できます。

トリガプロシージャからの戻り値は OK または SKIP という文字列、 または、Tcl の 'array get' コマンドにより返されるリストのいずれか です。戻り値が OK ならば、このトリガを発行した通常の( INSERT/ UPDATE/DELETE といった)操作は有効になります。反対に、SKIP ならば、 トリガマネージャに黙ってその操作を差し止めるように通知します。 'array get' から得られるリストは、PL/Tcl によって変更され、$NEW と して与えられたものの代わりに挿入される行を、トリガマネージャに 返すように通知します(INSERT/UPDATEのみです)。いうまでもなく、これ ら全ては、トリガが BEFORE か FOR EACH ROW の場合にのみ意味があり ます。

テーブル内のある整数値を行へ行なわれた更新回数を保持するようにさ せるトリガプロシージャの小さな例をここに示します。新しい行の挿 入の場合は、この値は 0 に初期化され、その後の更新操作時にインクリ メントされます。

    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, desc text);

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

PL/Tcl からのデータベースへのアクセス

PL/Tcl プロシージャ本体からデータベースにアクセスできるようにす るコマンドには以下のものがあります。

elog レベル メッセージ

ログメッセージを発行します。レベルとして C の elog() 関数と同様 に、NOTICE、WARN、ERROR、FATAL、DEBUG、NOIND を指定できます。

quote 文字列

現れる全てのシングルクォートとバックスラッシュ文字を二重にします。 変数が、spi_exec または spi_prepare へ与えられる問い合わせ用文字列と して使われる場合、これが使用されるべきです。( spi_execp の値リスト 用には使用されるべきではありません。)次のような問い合わせ文字列におい て、Tcl 変数 val が "doesn't" を持つ場合を考えて下さい。

    "SELECT '$val' AS ret"
    
最終的に、次のような問い合わせ文字列になり、spi_exec または spi_prepare 実行中にパースエラーが発生します。
    "SELECT 'doesn't' AS ret"
    
これは
    "SELECT 'doesn''t' AS ret"
    
となるべきですので、次のように記述される必要があります。
    "SELECT '[ quote $val ]' AS ret"
    

spi_exec ?-count n? ?-array name? query ?loop-body? spi_exec ?-count n? ?-array name? 問い合わせ ?loop-body?

問い合わせ用のパーサ/プランナ/オプティマイザ/エクゼキュータを呼び出 します。オプションの -count 値は、spi_exec に問い合わせで処理される 行の最大数を通知します。

問い合わせが SELECT 文であり、オプションの loop-body ( foreach 文に似た Tcl コマンドの集合)が指定された場合、選択された行を一つ 一つ評価し、continue/break で期待されるように振舞います。選択され たフィールドの値は、カラム名と同じ名前の変数に代入されます。ですの で、

    spi_exec "SELECT count(*) AS cnt FROM pg_proc"
    
は、pg_proc システムカタログの行数を $cnt 変数に設定します。-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 問い合わせ 型リスト

後の実行用に問い合わせ計画を準備し、保存します。C レベルの SPI_prepare とは少し異なり、その計画は自動的に最上位のメモリコンテ キストにコピーされます。従って、現時点では保存せずに準備を行なうこ とはできません。

問い合わせが引数を参照する場合、その型の名前は Tcl のリストとして与 えられる必要があります。spi_prepare の戻り値は問い合わせ ID であり、 続いて spi_execp を呼び出す時に使われます。実例については spi_execp を見て下さい。

spi_exec ?-count n? ?-array name? ?-nulls str? 問い合わせ ?値リスト? ?loop-body?

spi_prepare によって準備された計画を変数に置き換えて実行します。 -count オプションの値は spi_execp に問い合わせによって処理される行 の最大数を通知します。

-nulls オプションの値は空白と 'n' 文字による文字列で、spi_execp にどの値が NULL かを通知します。指定された場合、正確に値の数の分 の文字列長である必要があります。

queryid は spi_prepare 呼び出しの戻り値であるIDです。

spi_prepare に型リストが与えられていた場合、そのリストの大きさと 全く同じ大きさの値リストを query の後ろに 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 においてもバックスラッシュを主パーサが処理をするからです。 spi_prepare に渡す問い合わせ文字列の内側では、ドル記号をパラメー タの位置を指定するために使用すべきであり、最初の関数呼び出しで与え られる値で $1 が置換されないようにすべきです。

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

PL/Tcl は、多く使われる事柄について特別なサポートを行ないます。 pltcl_modules と pltcl_modfuncs という2つの魔法のようなテーブルを 認知します。これらがある場合、'unknown' というモジュールが作成直後 にインタプリタに読み込まれます。不明な Tcl プロシージャが呼び出され ると、その不明なプロシージャがそのモジュール内に定義された物かどう か尋ねられます。真ならば要求に応じてモジュールが読み込まれます。こ の動作を有効にするには、-DPLTCL_UNKNOWN_SUPPORT を付けて PL/Tcl 呼 び出しハンドラをコンパイルしなくてはいけません。

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