無料スクリプト配布のPHP.TO   PHPの実用的なtips PHPマニュアル MySQLマニュアル Apacheマニュアル PostgreSQLマニュアル マニュアル検索    

35.9. C言語関数

ユーザ定義の関数はC(もしくはC++のようなCと互換性のある言語)で作成することができます。 そのような関数は動的ロード可能オブジェクト(共有ライブラリとも呼ばれます)としてコンパイルされ、必要に応じてサーバにロードされます。 動的ロード機能が、 "C言語" 関数を "内部" 関数と区別するものです。 コーディング方法は基本的に両方とも同じです。 (したがって、標準内部関数ライブラリはユーザ定義のC関数のコーディング例の豊富な情報源となります。)

現在、2つの異なる呼び出し規約がC関数で使用されています。 より新しい "Version 1" 呼び出し規約は、以下に示すように、その関数用に呼び出しマクロ PG_FUNCTION_INFO_V1() を書くことで示されます。 このマクロが存在しなければ、旧形式( "Version 0" )の関数であることを示します。 どちらの場合も CREATE FUNCTION で指定する言語名は C です。 旧形式の関数は移植性の問題と機能の不足のために勧められません。 これは現在、互換性の理由のために存在しています。

35.9.1. 動的ロード

特定のロード可能オブジェクト内のユーザ定義の関数がセッションで最初に呼び出されると、動的ローダは、その関数を呼び出すことができるように、オブジェクトファイルをメモリ内に読み込みます。 そのため、ユーザ定義のC関数用の CREATE FUNCTION はその関数について、ロード可能オブジェクトファイルの名前とオブジェクトファイル中の呼び出される特定の関数のC名称(リンクシンボル)という2つの情報を指定しなければなりません。 C名称が明示的に指定されなかった場合、SQLにおける関数名と同じものと仮定されます。

CREATE FUNCTION コマンドで与えられた名前に基づいて、共有オブジェクトファイルの場所を見つける際に以下のアルゴリズムが使用されます。

  1. 名前が絶対パスの場合、指定されたファイルが読み込まれます。

  2. 名前が $libdir という文字列から始まる場合、その部分は PostgreSQL パッケージのライブラリディレクトリで置き換えられます。 このディレクトリはビルド時に決定されます。

  3. 名前にディレクトリ部分がない場合、そのファイルは dynamic_library_path 設定変数で指定されたパス内から検索されます。

  4. 上記以外の場合(ファイルがパス内に存在しない場合や相対ディレクトリ部分を持つ場合)、動的ローダは指定された名前をそのまま使用し、ほとんどの場合は失敗します。 (これは現在の作業ディレクトリに依存するため信頼できません。)

ここまでの流れがうまくいかなかった場合、プラットフォーム独自の共有ライブラリファイル拡張子(多くの場合 .so )が指定された名前に追加され、再度この流れを試みます。 同様に失敗した場合は、読み込みは失敗します。

共有ライブラリを $libdir から相対的に、もしくは動的ライブラリパスの通った所に配置することを推奨します。 異なる場所に新しいインストレーションを配置する場合にバージョンアップを簡単にします。 $libdir が示す実際のディレクトリは pg_config --pkglibdir コマンドを使用することでわかります。

PostgreSQL サーバの実効ユーザIDはロード予定のファイルのパスまで到達できなければなりません。 よくある失敗として、 postgres ユーザに対して読み込み、実行、または両方の権限がそのファイルとその上位ディレクトリに与えられていないことがあります。

どの場合でも、 CREATE FUNCTION コマンドに与えたファイル名はそのままシステムカタログに保存されます。 ですので、もしそのファイルを再度読み込む必要がある場合、同じ処理が適用されます。

注意: PostgreSQL はC関数を自動的にコンパイルしません。 CREATE FUNCTION コマンドで参照する前に、そのオブジェクトファイルはコンパイルされていなければなりません。 さらなる情報については 項35.9.6 を参照してください。

確実に、動的にロードされるモジュールが互換性がないサーバにロードされないように、 PostgreSQL は、そのファイルに適切な内容を持つ "魔法のブロック" が含まれているかどうか検査します。 これによりサーバは、メジャーバージョンが異なる PostgreSQL 用にコンパイルされたモジュールなど、明確に互換性がないことを検知することができます。 魔法のブロックは PostgreSQL 8.2から要求されています。 魔法のブロックを含めるためには、以下をモジュールのソースファイルに一度(一度だけ)、 fmgr.h ヘッダファイルをincludeさせた後で、記述してください。

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

そのコードをリリース8.2より前の PostgreSQL 用にコンパイルする必要がなければ、 #ifdef テストを省略することができます。

最初に使用された後も、動的にロードされたオブジェクトファイルはメモリ内に保持されます。 同一セッションにおいてそのファイル内の関数をその後に呼び出した場合、シンボルテーブルの検索に要する小さなオーバーヘッドしかかかりません。 例えば再コンパイルした後など、そのオブジェクトファイルを強制的に再度読み込ませる必要がある場合は、新しいセッションを開始してください。

省略することもできますが、動的にロードされるファイルに初期化処理関数と最終処理関数を含めることができます。 _PG_init という関数がファイルに存在すると、この関数はファイルがロードされた直後に呼び出されます。 この関数は引数を取らずvoid型を返さなければなりません。 _PG_fini という関数がファイルに存在すると、この関数はファイルがアンロードされる直前に呼び出されます。 この関数も同様に引数を取らずvoid型を返さなければなりません。 _PG_fini がファイルのアンロード時にのみ呼び出されるものであり、処理の終了時に呼び出されるものではないことに注意してください。 (現在、アンロードは無効となっていますので、決して発生しません。将来変更される可能性があります。)

35.9.2. C言語関数における基本型

C言語関数の作成方法を理解するためには、 PostgreSQL が基本データ型を内部でどのように表現し、どのようにそれらを関数とやり取りしているかを理解する必要があります。 内部的に PostgreSQL は基本型を "メモリの小さな塊" とみなします。 ある型を定義するユーザ定義関数は、言い換えると、 PostgreSQL がそれを操作できる方法を定義します。 つまり、 PostgreSQL はデータの格納、ディスクからの取り出しのみを行い、データの入力や処理、出力にはユーザ定義関数を使用します。

基本型は下記の3つのいずれかの内部書式を使用しています。

  • 固定長の値渡し

  • 固定長の参照渡し

  • 可変長の参照渡し

値渡しは、1、2、4バイト長の型のみで使用することができます(使用するマシンの sizeof(Datum) が8の場合は8バイトも使用できます)。 データ型を定義する際、その型がすべてのアーキテクチャにおいて同一の大きさ(バイト数)となるように定義するように注意してください。 例えば、 long 型はマシンによっては4バイトであったり、8バイトであったりして危険ですが、 int 型はほとんどのUnixマシンでは4バイトです。 Unixマシンにおける int4 の理論的な実装は以下のようになります。

/* 4 バイト整数、値渡し */
typedef int int4;

(実際のPostgreSQLのCコードではこの型を int32 と呼びます。 int XX XX ビット であることはCにおける規約だからです。 したがって int8 というCの型のサイズは1バイトであることに注意してください。 int8 というSQLの型はCでは int64 と呼ばれます。 表35-1 も参照してください。)

一方、任意の大きさの固定長の型は参照として引き渡すことができます。 例として以下に PostgreSQL の型の実装サンプルを示します。

/* 16 バイト構造体、参照渡し */
typedef struct
{
    double  x, y;
} Point;

それらの型のポインタのみが PostgreSQL 関数の入出力時に使用できます。 それらの型の値を返すためには、 palloc() を使用して正しい大きさのメモリ領域を割り当て、そのメモリ領域に値を入力し、それのポインタを返します。 (また、入力引数の1つと同じ型かつ同じ値を返したいのであれば、 palloc を行う手間を省くことができます。 この場合は入力値へのポインタを単に返してください。)

最後に、すべての可変長型は参照として引き渡す必要があります。 また、すべての可変長型は正確に4バイトの不透明なlengthフィールドから始まる必要があります。 このフィールドは SET_VARSIZE で設定されます。決して直接このフィールドを設定してはいけません。 その型に格納されるすべてのデータはlengthフィールドのすぐ後のメモリ領域に置かれる必要があります。 lengthフィールドにはその構造体の総長が格納されます。つまり、lengthフィールドそのものもその大きさに含まれます。

この他の重要な点は、データ型の値の中で初期化されていないビットを残さないことです。 例えば、構造体内に存在する可能性がある整列用のパディングバイトを注意してすべてゼロクリアしてください。 こうしないと、独自データ型の論理的に等価な定数がプランナにより一致しないものと判断され、(不正確ではありませんが)非効率的な計画をもたらすかもしれません。

警告

参照渡しの入力値の内容を 決して 変更しないでください。 指定したポインタがディスクバッファを直接指し示している可能性がよくありますので、変更すると、ディスク上のデータを破壊してしまうかもしれません。 この規則の唯一の例外について 項35.10 で説明します。

例えば、 text 型を定義するには、下記のように行えます。

typedef struct {
    int32 length;
    char data[1];
} text;

ここで宣言されたdataフィールドは、明らかにすべての取り得る文字列を保持できる長さではありません。 C言語 では可変長の構造体を定義することは不可能ですので、 C コンパイラは配列の添字の範囲検査を行わないという事実に依存します。 必要な領域量を割り当て、あたかも正しい長さで宣言されたかのように、配列としてアクセスするだけです。 (この手法はよく使用されます。C言語に関する多くの書籍で説明されています。)

可変長型を操作する時、正確な大きさのメモリを割り当て、lengthフィールドを正確に設定することに注意する必要があります。 例えば、40バイトを text 構造体に保持させたい場合、下記のようなコードを使用します。

#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);
...

VARHDRSZ sizeof(int32) と同一ですが、可変長型のオーバーヘッド分の大きさを参照する時には、 VARHDRSZ マクロを使用する方が好ましい形式とみなされています。 また長さフィールドを単なる代入ではなく SET_VARSIZE マクロを使用して設定 しなければなりません

表35-1 は、 PostgreSQL の組み込み型を使用するC言語関数を作成する時の、Cの型とSQL型との対応を規定したものです。 "定義場所" 列では、型定義を取り出すためにインクルードしなければならないヘッダファイルを示しています。 (実際の定義は一覧中のファイルからインクルードされた、別のファイルであるかもしれません。 ユーザは定義されたインタフェースを厳守することを推奨されています。) postgres.h には必ず必要になる多くのものが宣言されていますので、ソースファイルの中で必ず初めにこのファイルをインクルードしなければならないことに注意してください。

表 35-1. 組み込みSQL型に相当するCの型

SQL型 C 言語型 定義場所
abstime AbsoluteTime utils/nabstime.h
boolean bool postgres.h (コンパイラで組み込み済みの可能性があります)
box BOX* utils/geo_decls.h
bytea bytea* postgres.h
"char" char (コンパイラで組み込み済み)
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
reltime RelativeTime utils/nabstime.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
tinterval TimeInterval utils/nabstime.h
varchar VarChar* postgres.h
xid TransactionId postgres.h

ここまでで基本型に関してあり得る構造体のすべてを記述しましたので、実際の関数の例をいくつか示すことができます。

35.9.3. Version 0 呼び出し規約

まず最初に、現在は非推奨ですが理解しやすいので、 "古いスタイル" の呼び出し規約を説明します。 Version-0メソッドでは、C関数の引数と結果は、通常のCのプログラムの記述の方法と同じような形式で行いますが、上記の説明のように、各SQLのデータ型に対するC言語での表現を注意して使用してください。

以下にいくつか例を示します。

#include "postgres.h"
#include <string.h>
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* 値渡し */
         
int
add_one(int arg)
{
    return arg + 1;
}

/* 固定長の参照渡し */

float8 *
add_one_float8(float8 *arg)
{
    float8    *result = (float8 *) palloc(sizeof(float8));

    *result = *arg + 1.0;
       
    return result;
}

Point *
makepoint(Point *pointx, Point *pointy)
{
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;
       
    return new_point;
}

/* 可変長の参照渡し */

text *
copytext(text *t)
{
    /*
     * VARSIZEは構造体の総長をバイト数で表したものです。
     */
    text *new_t = (text *) palloc(VARSIZE(t));
    SET_VARSIZE(new_t, VARSIZE(t));
    /*
     * VARDATAは構造体のデータ領域へのポインタです。
     */
    memcpy((void *) VARDATA(new_t), /* コピー先 */
           (void *) VARDATA(t),     /* コピー元 */
           VARSIZE(t) - VARHDRSZ);  /* バイト数 */
    return new_t;
}

text *
concat_text(text *arg1, text *arg2)
{
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
    return new_text;
}

上のコードが funcs.c というファイルに用意され、共有オブジェクトとしてコンパイル済みであるとすると、以下のようなコマンドで PostgreSQL の関数を定義することができます。

CREATE FUNCTION add_one(integer) RETURNS integer
     AS '

DIRECTORY

/funcs', 'add_one'
     LANGUAGE C STRICT;

-- "add_one"というSQL関数名をオーバーロードしていることに注意
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 チュートリアルディレクトリ)を表します。 ( AS 句中では単に 'funcs' を使用し、後で DIRECTORY を検索パスに追加する方がより良い方法です。 どの場合でも、一般的に .so .sl が使用される、共有ライブラリ用のシステム独特の拡張子を省略することができます。)

ここで、関数を "厳密(strict)" と指定していることに注目してください。 これは、もし入力された値がNULLであった場合に、システムが自動的に返り値もNULLであるとみなすことを意味します。 これを行うことによって、関数のコードで入力値がNULLであるかどうかの検査を行う必要がなくなります。 これがなければ、参照渡し引数それぞれに対してヌルポインタについての検査を行うなど、NULL値の明示的な検査を行う必要性が出てきます。 (値渡し引数に関しては、検査を行う方法は存在しません。)

この呼び出し規約は単純ですが、この方法は移植性の面であまり優れていません。 この方法で int 型より小さいデータ型を渡す部分で問題を抱えているアーキテクチャも存在します。 また、関数の結果としてNULLを返す簡単な方法はありません。 その上、NULL引数をうまく処理する方法としては、関数を厳密なものにする以外方法はありません。 次に説明するVersion-1規約ではこれらの問題が解決されています。

35.9.4. Version 1 呼び出し規約

Version-1呼び出し規約では、引数と結果の引き渡しの複雑さをなくすためにマクロを使用しています。 Version-1関数のC言語宣言は必ず下記のように行います。

Datum funcname(PG_FUNCTION_ARGS)

さらに、マクロ呼び出し

PG_FUNCTION_INFO_V1(funcname);

が同じソースファイルに書かれている必要があります。 (一般には、関数の直前に書かれます。) PostgreSQL ではすべての内部関数はVersion-1であると認識するので、このマクロの呼び出しは internal 言語関数では必要ありません。 しかし、動的にロードされる関数では必要です。

Version-1関数では、それぞれの実引数は、引数のデータ型に合った PG_GETARG_ xxx () マクロを使用して取り出され、結果は戻り値の型に合った PG_RETURN_ xxx () マクロを使用して返されます。 PG_GETARG_ xxx () は、その引数として、取り出す関数引数の番号(ゼロから始まります)を取ります。 PG_RETURN_ xxx () は、その引数として、実際に返す値を取ります。

上記と同じ関数をVersion-1形式で記述したものを以下に示します。

#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* 値渡し */

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用のマクロは参照渡しという性質を隠します */
    float8   arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

PG_FUNCTION_INFO_V1(makepoint);

Datum
makepoint(PG_FUNCTION_ARGS)
{
    /* ここのPoint型の参照渡しという性質は隠されていません */
    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_P(0);
    /*
     * VARSIZEは構造体の総長をバイト数で表したものです。
     */
    text     *new_t = (text *) palloc(VARSIZE(t));
    SET_VARSIZE(new_t, VARSIZE(t));
    /*
     * VARDATAは構造体のデータ領域へのポインタです。
     */
    memcpy((void *) VARDATA(new_t), /* コピー先 */
           (void *) VARDATA(t),     /* コピー元 */
           VARSIZE(t) - VARHDRSZ);  /* バイト数 */
    PG_RETURN_TEXT_P(new_t);
}

PG_FUNCTION_INFO_V1(concat_text);

Datum
concat_text(PG_FUNCTION_ARGS)
{
    text  *arg1 = PG_GETARG_TEXT_P(0);
    text  *arg2 = PG_GETARG_TEXT_P(1);
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1) - VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1) - VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
    PG_RETURN_TEXT_P(new_text);
}

CREATE FUNCTION コマンドはVersion-0と同じものです。

一見Version-1のコーディング規約は無意味なものに見えるかもしれません。 しかし、マクロが必要のない情報を隠蔽しているので、多数の改良が行われています。 例えば、 add_one_float8 のコードでは、 float8 が参照渡しであることを意識する必要がなくなっています。 また別の例としては、可変長型の GETARG マクロは "TOASTされた" (圧縮または行外)値をより効率的に取り出すことができます。

Version-1関数の1つの大きな改善点は、NULLの入力/結果の処理能力です。 PG_ARGISNULL( n ) マクロにより関数は各入力がNULLであるかどうかの検査を行うことができます。 (もちろんこれは、 "厳密" と宣言されていない関数でのみ必要です。) PG_GETARG_ xxx () マクロと同様、入力引数の番号はゼロから始まります。 引数がNULLでないことを確認するまでは、 PG_GETARG_ xxx () の実行は控えなければなりません。 結果としてNULLを返す場合は、 PG_RETURN_NULL() を実行します。 これは、厳密な関数と厳密でない関数の両方で使用可能です。

新しい形式のインタフェースでは、その他のオプションとして PG_GETARG_ xxx () マクロの変形を2つ提供しています。 1つ目の PG_GETARG_ xxx _COPY() によって、安全に書き込むことができる指定引数のコピーが確実に返されます。 (通常のマクロは、物理的にテーブルに格納されている値へのポインタを返すことがあるので、書き込んではなりません。 PG_GETARG_ xxx _COPY() マクロの結果は書き込み可能であることが保証されています。) 2つ目の変形は、引数を3つ取る PG_GETARG_ xxx _SLICE() マクロからなります。 1つ目は関数の引数の番号(上記の通り)です。 2つ目と3つ目は、オフセットと返されるセグメントの長さです。 オフセットはゼロから始まり、負の長さは残りの値を返すことを要求します。 これらのマクロを使用すると、ストレージ種類が "external" (外部)である大きな値の一部へアクセスする際に非常に効果的です。 (列のストレージ種類は ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype を使用して指定できます。 storagetype は、 plain external extended 、または main のいずれかです。)

最後に、Version-1関数呼び出し規約では、結果集合( 項35.9.9 )を返すこと、およびトリガ関数( 第36章 )と手続型言語の呼び出しハンドラ( 第51章 )を実装することができます。 また、Version-1コードは、標準Cの関数呼び出しプロトコルの制約を守りますので、Version-0よりも移植性があります。 詳細についてはソース配布物内の src/backend/utils/fmgr/README を参照してください。

35.9.5. コードの作成

より先進的な話題に入る前に、 PostgreSQL C言語関数のコーディングについての規則をいくつか説明します。 C言語以外の言語で記述した関数を PostgreSQL に組み込みむことは可能であるかもしれませんが、例えばC++、FORTRANやPascalといった言語はC言語と同じ呼び出し規約に従いませんので、多くの場合、(可能であったとしても)困難です。 それはつまり、他の言語では同じ方法で関数に引数を渡したり、関数から結果を返すことを行わないということです。 このため、C言語関数は実際に C 言語で書かれているものと仮定します。

C関数の作成と構築の基本規則を以下に示します。

  • pg_config --includedir-server を使用して、使用中のシステム(もしくはユーザが実行するシステム)にて PostgreSQL サーバのヘッダファイルがインストールされた場所を見つけます。

  • PostgreSQL に動的にロードできるように独自コードをコンパイル/リンクする時には常に、特別なフラグが必要となります。 特定のオペレーティングシステムにおけるコンパイル/リンク方法については 項35.9.6 を参照してください。

  • 忘れずに 項35.9.1 で説明した "魔法のブロック" を共有ライブラリで定義してください。

  • メモリを割り当てる際、 C ライブラリの malloc free ではなく、 PostgreSQL palloc pfree を使用してください。 palloc で割り当てられたメモリは各トランザクションの終わりに自動的に解放され、メモリリークを防ぎます。

  • memset を使用して、構造体を必ずゼロクリアしてください(または最初の段階で palloc0 を用いて割り当ててください)。 構造体の各フィールドを割り当てたとしても、ゴミの値を持つ整列用のパディング(構造体内の穴)があるかもしれません。 こうしないと、ハッシュインデックスやハッシュ結合をサポートすることが困難です。 ハッシュを計算するには、データ構造体内の有意なビットのみを取り出す必要があるためです。 プランナはまた時折ビット単位の等価性を用いて定数の比較を行います。 このため論理的にな値がビット単位で等価でない場合に望まない計画になってしまう可能性があります。

  • ほとんどの PostgreSQL の内部型は postgres.h に宣言されています。 一方、関数管理インタフェース( PG_FUNCTION_ARGS など)は fmgr.h で宣言されています。 したがって、少なくともこの2つのファイルをインクルードする必要があります。 移植性に関する理由により、 postgres.h をその他のシステムヘッダファイル、ユーザヘッダファイルよりも 先に インクルードしておくことが最善です。 postgres.h をインクルードすることは elog.h palloc.h もインクルードすることになります。

  • オブジェクトファイルで定義されているシンボル名は、互いに、または PostgreSQL サーバの実行ファイルで定義されているものと異なっている必要があります。 これに関するエラーが表示される場合は、関数名または変数名を変更する必要があります。

35.9.6. 動的にロードされる関数のコンパイルとリンク

Cで書かれた PostgreSQL の拡張関数を使うためには、サーバが動的にロードできるように特別な方法でコンパイルとリンクを行う必要があります。 正確には 共有ライブラリ を作る必要があります。

本節の説明以上の詳しい情報はオペレーティングシステムのドキュメント、特にCコンパイラ cc とリンクエディタ ld のマニュアルページを参照してください。 さらに、 PostgreSQL のソースコードの contrib ディレクトリにいくつか実例があります。 しかし、もしこれらの例に頼ると PostgreSQL ソースコードが利用できることに依存したモジュールが作られてしまいます。

共有ライブラリの作成は一般的に実行プログラムのリンクに類似しています。 まずソースファイルがオブジェクトファイルにコンパイルされ、そのオブジェクトファイル同士がリンクされます。 これらのオブジェクトファイルは 位置独立なコード PIC )として作られる必要があります。 それは概念的には、実行プログラムから呼び出される時にメモリの適当な場所に置くことができるということです (実行プログラム用として作られたオブジェクトファイルはそのようにはコンパイルされません)。 共有ライブラリをリンクするコマンドは実行プログラムのリンクと区別するための特別なフラグがあります(少なくとも理論上ではそのようになっています。他のシステムではもっと醜い実際が見受けられます)。

次の例ではソースコードは foo.c ファイルにあると仮定し、 foo.so という共有ライブラリを作るとします。 中間のオブジェクトファイルは特別な記述がない限り foo.o と呼ばれます。 共有ライブラリは1つ以上のオブジェクトファイルを持つことができますが、ここでは1つしか使いません。

FreeBSD

PIC を作るためのコンパイラフラグは -fpic です。 共有ライブラリを作るコンパイラフラグは -shared です。

gcc -fpic -c foo.c
gcc -shared -o foo.so foo.o

これは FreeBSD のバージョン3.0に適用されます。

HP-UX

PIC を作るためのシステムコンパイラのコンパイラフラグは +z です。 GCC を使う場合は -fpic です。 共有ライブラリのためのリンカフラグは -b です。 したがって、以下のようになります。

cc +z -c foo.c

または

gcc -fpic -c foo.c

そして

ld -b -o foo.sl foo.o

HP-UX は他のほとんどのシステムと異なり共有ライブラリに .sl という拡張子を使います。

IRIX

PIC がデフォルトで、特別なコンパイラオプションは何も必要ありません。 共有ライブラリを作るためのリンカオプションは -shared です。

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

Linux

PIC を作るためのコンパイラフラグは -fpic です。 いくつかのプラットフォームでは状況によって -fpic が動作しない場合は -fPIC を使わなければいけません。 さらに詳しい情報についてはGCCのマニュアルを参照してください。 共有ライブラリを作るコンパイラフラグは -shared です。 完全な例は下記のようになります。

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

Mac OS X

例を以下に示します。 開発者用ツールがインストールされていることが前提です。

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

NetBSD

PIC を作るためのコンパイラフラグは -fpic です。 ELF システムでは -shared コンパイラフラグを使用して共有ライブラリをリンクします。 より古い非ELFシステムでは ld -Bshareable が使われます。

gcc -fpic -c foo.c
gcc -shared -o foo.so foo.o

OpenBSD

PIC を作成するためのコンパイラフラグは -fpic です。 共有ライブラリをリンクするには ld -Bshareable を使用します。

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

Solaris

PIC を作るためのコンパイラフラグはSunコンパイラでは -KPIC で、 GCC では -fpic です。 共有ライブラリをリンクするためには、どちらのコンパイラでもコンパイラオプションは -G で、 GCC の場合、代わりに -shared オプションを使うこともできます。

cc -KPIC -c foo.c
cc -G -o foo.so foo.o

もしくは

gcc -fpic -c foo.c
gcc -G -o foo.so foo.o

Tru64 UNIX

PIC はデフォルトで、コンパイルコマンドは通常のものです。 リンクのためには特別なオプション付きの ld を使用します。

cc -c foo.c
ld -shared -expect_unresolved '*' -o foo.so foo.o

システムのコンパイラではなくGCCを使う場合も同じ手順です。 特別のオプションは必要ありません。

UnixWare

PIC を作るためのコンパイラフラグはSCOコンパイラでは -KPIC で、 GCC では -fpic です。 共有ライブラリのリンクは、SCOコンパイラではコンパイラオプションは -G で、 GCC では -shared です。

cc -K PIC -c foo.c
cc -G -o foo.so foo.o

もしくは

gcc -fpic -c foo.c
gcc -shared -o foo.so foo.o

ティップ: これがあまりに難しいようであれば、 GNU Libtool の使用を検討すべきです。 これはプラットフォームの違いを、統一されたインタフェースで判らないようにします。

これで完成した共有ライブラリファイルは PostgreSQL にロードすることができます。 CREATE FUNCTION コマンドにファイル名を指定する時には、中間オブジェクトファイルではなく共有ライブラリファイル名の名前を与えてください。 システムの標準共有ライブラリ用の拡張子(通常 .so あるいは .sl )は CREATE FUNCTION で省略することができ、そして移植性を最も高くするため通常は省略されます。

サーバがライブラリファイルをどこに見つけるかに関しては 項35.9.1 を見直してください。

35.9.7. 複合型引数

複合型ではCの構造体のような固定のレイアウトがありません。 複合型のインスタンスはNULLフィールドを持つことができます。 さらに、複合型で継承階層の一部であるものは、同じ継承階層の他のメンバとは異なるフィールドを持つこともできます。 そのため、 PostgreSQL はC言語から複合型のフィールドにアクセスするための関数インタフェースを提供します。

以下のような問い合わせに答える関数を書こうとしていると仮定します。

SELECT name, c_overpaid(emp, 1500) AS overpaid
    FROM emp
    WHERE name = 'Bill' OR name = 'Sam';

Version 0呼び出し規約を使用すると、 c_overpaid は以下のように定義できます。

 
#include "postgres.h"
#include "executor/executor.h"  /* GetAttributeByName()用 */

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

bool
c_overpaid(HeapTupleHeader t, /* empの現在の行 */
           int32 limit)
{
    bool isnull;
    int32 salary;
 
    salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull));
    if (isnull)
        return false;
    return salary > limit;
}

Version 1で作成すると、上の関数は以下のようになります。

 
#include "postgres.h"
#include "executor/executor.h"  /* GetAttributeByName()用 */

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

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がNULLの場合用にPG_RETURN_NULL()を行った方が良いでしょう */

    PG_RETURN_BOOL(DatumGetInt32(salary) > limit);
}
 

GetAttributeByName は、指定された行から属性を返す、 PostgreSQL システム関数です。 これには3つの引数があります。 それらは、関数に渡された HeapTupleHeader 型の引数、求められた属性の名前、属性がNULLであるかどうかを通知する返りパラメータです。 GetAttributeByName は適切な DatumGet XXX () マクロを使用して適切なデータ型に変換可能な Datum 型の値を返します。 このNULLフラグが設定されている場合、戻り値の意味がないことに注意し、この結果で何かを行おうとする前に常に、NULLフラグを検査してください。

対象列を名前ではなく列番号で選択する GetAttributeByNum もあります。

下記のコマンドで c_overpaid 関数をSQLで宣言します。

CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
    AS '

DIRECTORY

/funcs', 'c_overpaid'
    LANGUAGE C STRICT;

入力引数がNULLかどうかを検査する必要がないように STRICT を使用していることに注意してください。

35.9.8. 行(複合型)を返す

C言語関数から行もしくは複合型の値を返すために、複合型の複雑な作成のほとんどを隠蔽するマクロや関数を提供する、特別なAPIを使用することができます。 このAPIを使用するためには、ソースファイルで以下をインクルードする必要があります。

#include "funcapi.h"

複合型のデータ値(以降 "タプル" と記す)を作成する2つの方法があります。 Datum値の配列から作成する方法、もしくはタプルのある列の型の入力変換関数に渡すことができるC文字列の配列から作成することです。 どちらの方法でも、まずタプル構造体用の TupleDesc 記述子を入手、あるいは作成しなければなりません。 Datumを使用する場合は、 TupleDesc BlessTupleDesc に渡し、各行に対して heap_form_tuple を呼び出します。 C文字列を使用する場合は、 TupleDesc TupleDescGetAttInMetadata に渡し、各行に対して BuildTupleFromCStrings を呼び出します。 タプルの集合を返す関数の場合、この設定段階を最初の関数呼び出しで一度にまとめて行うことができます。

必要な TupleDesc の設定用の補助用関数がいくつかあります。 ほとんどの複合型を返す関数での推奨方法は、以下の関数を呼び出し、呼び出し元の関数自身に渡される fcinfo 構造体と同じものを渡すことです。

TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
                                   Oid *resultTypeId,
                                   TupleDesc *resultTupleDesc)

(これにはもちろん、version 1呼び出し規約を使用していることが必要です。) resultTypeId NULL とすることも、ローカル変数のアドレスを指定して関数の戻り値型のOIDを受け取ることができます。 resultTupleDesc はローカルな TupleDesc 変数のアドレスでなければなりません。 結果が TYPEFUNC_COMPOSITE かどうかを確認してください。 TYPEFUNC_COMPOSITE であった場合、 resultTupleDesc には必要な TupleDesc が格納されています。 ( TYPEFUNC_COMPOSITE ではなかった場合、 "レコード型を受け付けない文脈でレコードを返す関数が呼び出されました" というエラーを報告することができます。)

ティップ: get_call_result_type は、多様性関数の結果の実際の型を解決することができます。 ですので、複合型を返す関数だけではなく、スカラの多様結果を返す関数でも有意です。 resultTypeId 出力は主にスカラの多様結果を返す関数で有意です。

注意: get_call_result_type は、 get_expr_result_type と似たような関数で、関数呼び出しで想定される出力型を式のツリー構造として解決します。 関数自身以外から結果型を決定したい場合に、これを使用することができます。 また、 get_func_result_type という関数もあります。 これは関数のOIDが利用できる場合にのみ使用することができます。 しかし、これらの関数は、 record 型を返すものと宣言された関数では使用できません。 また、 get_func_result_type は多様型を解決することができません。 したがって、優先して get_call_result_type を使用すべきです。

古く、廃止予定の TupleDesc を入手するための関数を以下に示します。

TupleDesc RelationNameGetTupleDesc(const char *relname)

これを指名したリレーションの行型用の TupleDesc を取り出すために使用してください。 また、

TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)

これを型のOIDに基づいて TupleDesc を取り出すために使用してください。 これは、基本型もしくは複合型の TupleDesc を取り出すために使用可能です。 これは record を返す関数ではうまく動作しません。 また、多様型を解決することもできません。

TupleDesc を獲得した後に、Datumを使用する場合は以下を呼び出してください。

TupleDesc BlessTupleDesc(TupleDesc tupdesc)

C文字列を使用する場合は以下を呼び出してください。

AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)

集合を返す関数を作成する場合は、これらの関数の結果を FuncCallContext 構造体に格納してください。 それぞれ tuple_desc attinmeta を使用します。

Datumを使用する場合は、ユーザデータをDatum形式に格納した HeapTuple を構築するために以下を使用します。

HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)

C文字列を使用する場合は、ユーザデータをC文字列形式に格納した HeapTuple を構築するために以下を使用します。

HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)

values は行の各属性を1要素としたC文字列の配列です。 各C文字列は、属性のデータ型用の入力関数が受け付け可能な形式でなければなりません。 属性の値をNULL値として返すためには、 values 配列の対応するポインタに NULL を設定してください。 この関数は返す行それぞれに対して繰り返し呼び出す必要があります。

関数から返すタプルを構築し終わったら、それを Datum に変換しなければなりません。 以下を使用して、 HeapTuple を有効なDatumに変換してください。

HeapTupleGetDatum(HeapTuple tuple)

単一行のみを返すのであれば、この Datum を直接返すことができます。 さもなくば、集合を返す関数における現在の戻り値として使用することができます。

次節に例を示します。

35.9.9. 集合を返す

C言語関数から集合(複数行)を返す機能のために特殊なAPIが用意されています。 集合を返す関数は、Version 1呼び出し規約に従う必要があります。 また、ソースファイルは上述の通り funcapi.h をインクルードする必要があります。

集合を返す関数( SRF )は返される項目ごとに呼び出されます。 そのため、 SRF は、過去の操作を記憶して呼び出しの度に次の項目を返すために十分な状態を保っている必要があります。 この処理を制御を補助するための FuncCallContext 構造体が備わっています。 関数内では、複数の呼び出しにまたがる FuncCallContext へのポインタを保持するには、 fcinfo->flinfo->fn_extra を使用します。

typedef struct
{
    /*
     * 既に行われた呼び出しの回数。
     * 
     * SRF_FIRSTCALL_INIT()によってcall_cntrが0に初期化され、
     * SRF_RETURN_NEXT()が呼び出される度に増分されます。
     */
    uint32 call_cntr;

    /*
     * 省略可能 : 呼び出しの最大数
     *
     * max_callsは、便宜上用意されているだけで、設定は省略可能です。
     * 設定されていなければ、関数が終了したことを知るための別の方法を
     * 用意する必要があります。
     */
    uint32 max_calls;

    /*
     * 省略可能 : 結果スロットへのポインタ
     * 
     * これは廃止され、後方互換性、すなわち非推奨のTupleDescGetSlot()を使用する
     * ユーザ定義のSRFのためにだけ存在します。
     */
    TupleTableSlot *slot;

    /*
     * 省略可能 : 様々なユーザによるコンテキスト情報へのポインタ
     * 
     * user_fctxは、関数の呼び出し間の任意のコンテキスト情報
     * を取得するためのユーザ独自の構造へのポインタとして使用されます。
     */
    void *user_fctx;

    /*
     * 省略可能 : 属性型入力メタ情報を含んだ構造体へのポインタ
     * 
     * attinmeta はタプル(つまり複合データ型)を返す際に使用され、
     * 基本データ型を返す場合には必要ありません。 
     * BuildTupleFromCStrings()を使用して返されるタプルを作成する場合にのみ必要です。
     */
    AttInMetadata *attinmeta;

    /*
     *  複数の呼び出しで必要とされる構造体に使われるメモリコンテキスト
     *
     * multi_call_memory_ctxは、SRF_FIRSTCALL_INIT()によってに設定され、
     * SRF_RETURN_DONE()がクリーンアップの際に使用します。 
     * これはSRFの複数呼び出しで再利用される全てのメモリ用に最も適切なメモリコンテキストです。
     */
    MemoryContext multi_call_memory_ctx;
    /*
     * 省略可能: タプル説明を含む構造体へのポインタ。
     * tuple_descはタプル(つまり複合データ型)を返す場合に使用され、BuildTupleFromCStrings()
     * ではなくheap_form_tuple()を使用してタプルを作成する場合にのみ必要です。
     * 通常ここに格納されるTupleDescは最初にBlessTupleDesc()を最初に実行したものでなければなり
     * ません。
     */
    TupleDesc tuple_desc;

} FuncCallContext;

SRF はいくつかの関数およびマクロを使用して FuncCallContext 構造体を自動的に操作します(また fn_extra で検索することを想定します)。

SRF_IS_FIRSTCALL()

を使用して、その関数呼び出しが初回のものであるか、2回目以降であるかを判断します。 最初の呼び出し(のみ)で、

SRF_FIRSTCALL_INIT()

を使用して、 FuncCallContext を初期化します。 最初の呼び出しを含むすべての呼び出しで、

SRF_PERCALL_SETUP()

を使用して、 FuncCallContext を使用するための適切な設定を行い、以前の受け渡しから残っている結果データを消去します。

関数で返すべきデータがある場合は、

SRF_RETURN_NEXT(funcctx, result)

を使用して、そのデータを呼び出し側に返します。 (先に説明した通り result Datum 型、つまり1つの値またはタプルである必要があります。) 最後に、関数がデータを返し終わったら、

SRF_RETURN_DONE(funcctx)

を使用して SRF を片付け、終了します。

SRF の呼び出し時に現行になっているメモリコンテキストは一時的なコンテキストで、各呼び出しの間に消去されます。 つまり palloc を使用して割り当てたもののすべてを pfree する必要はありません。 これらはいずれ消去されるものだからです。 しかし、データ構造体を複数の呼び出しに渡って使用するように割り当てる場合は、どこか別の場所に置いておく必要があります。 multi_call_memory_ctx によって参照されるメモリコンテキストは、 SRF の実行が終わるまで使用可能にしなければならないデータの保管場所として適しています。 つまり、ほとんどの場合、最初の呼び出しのセットアップ中に multi_call_memory_ctx へ切り替える必要があるということです。

完全な疑似コードの例を示します。

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);
        /* 一度限りのセットアップコードがここに入ります: */
        

user code


        

if returning composite


            

build TupleDesc, and perhaps AttInMetadata


        

endif returning composite


        

user code


        MemoryContextSwitchTo(oldcontext);
    }

    /* 毎回実行するセットアップコードがここに入ります: */
    

user code


    funcctx = SRF_PERCALL_SETUP();
    

user code



    /* これは、終了したかどうかをテストする方法の1つです: */
    if (funcctx->call_cntr < funcctx->max_calls)
    {
        /* ここで、別の項目を返します: */
        

user code


        

obtain result Datum


        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        /* これで項目を返し終わりました。 後はクリーンアップするだけです。 */
        

user code


        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;

     /* 関数の最初の呼び出し時にのみ実行 */
     if (SRF_IS_FIRSTCALL())
     {
        MemoryContext   oldcontext;

        /* 呼び出し間で永続化する関数コンテキストを作成 */
        funcctx = SRF_FIRSTCALL_INIT();

        /* 複数関数呼び出しに適切なメモリコンテキストへの切り替え */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        /* 返されるタプルの合計数 */
        funcctx->max_calls = PG_GETARG_UINT32(0);

        /*  結果型用のタプル記述子を作成 */
        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文字列からタプルを作成するために必要となる
         * 属性メタデータの生成
         */
        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* 全ての関数呼び出しで実行 */
    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;
    max_calls = funcctx->max_calls;
    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls)    /* 他にも送るものがある場合  */
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * 返すタプルを構築するためのvalues配列を用意します。
         * これは、後で適切な入力関数で処理される
         * C文字列の配列でなければなりません。
         */
        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));

        /* タプルの作成 */
        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    /* 何も残っていない場合 */
    {
        SRF_RETURN_DONE(funcctx);
    }
}

以下にこの関数をSQLで宣言する一例を示します。

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パラメータを使用する方法もあります。

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;

この方法では、関数の出力型は形式上無名の record 型になることに注意してください。

ソース配布物内の contrib/tablefunc モジュールのディレクトリには、集合を返す関数のより多くの例があります。

35.9.10. 引数と戻り値の多様性

C言語関数は、 anyelement anyarray anynonarray anyenum および anyrange 多様型を受け付ける、または返すように宣言することができます。 多様関数の詳細な説明は 項35.2.5 を参照してください。 関数の引数もしくは戻り値が多様型として定義される時、関数の作成者は前もって呼び出しにおけるデータ型や返すべきデータ型が何であるかを知ることはできません。 Version-1 C関数で引数の実データ型と、返すべきと想定された型を発見できるための2つのルーチンが fmgr.h に用意されています。 このルーチンは get_fn_expr_rettype(FmgrInfo *flinfo) get_fn_expr_argtype(FmgrInfo *flinfo, int argnum) という名前です。 これらは結果もしくは引数型のOIDを返します。 ただし、もし情報が利用できなければ InvalidOid を返します。 flinfo 構造体は通常 fcinfo->flinfo としてアクセスされます。 argnum パラメータは0から始まります。 また、 get_fn_expr_rettype の代わりに get_call_result_type を使用することもできます。 また、明示的な VARIADIC "any" キーワードを付けて呼び出されたかどうかを判定するために使用できる get_fn_expr_variadic があります。 これは特に後述の VARIADIC "any" の場合に有用です。

例えば、任意の型の単一要素を受け付け、その型の1次元配列を返す関数を考えてみます。

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

    /* 次元数は1 */
    ndims = 1;
    /* 要素を1つ */
    dims[0] = 1;
    /* 下限は1 */
    lbs[0] = 1;

    /* この要素型に関する必要情報を取り出す。 */
    get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);

    /* ここで配列を作成 */
    result = construct_md_array(&element, &isnull, ndims, dims, lbs,
                                element_type, typlen, typbyval, typalign);

    PG_RETURN_ARRAYTYPE_P(result);
}

以下のコマンドはSQLで make_array 関数を宣言します。

CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    AS '

DIRECTORY

/funcs', 'make_array'
    LANGUAGE C IMMUTABLE;

C言語関数でのみ使用できる多様性の変異体があります。 "any" 型のパラメータを取るように宣言できます。 (この型名は、SQL予約語でもあるため二重引用符で括らなくてはならないことに注意してください。) これは、他の "any" 引数が同じ型になることを強要することも、関数の結果型の決定を支援することもない点を除いて、 anyelement のように動作します。 C言語関数は最終パラメータが VARIADIC "any" であるように宣言可能です。 これは任意の型の1つ以上の実引数と一致します(同じ型である必要はありません)。 これらの引数は、通常のvariadic関数で起こったように、配列の中にまとめ られません 。 それらは単に別々に関数に渡されるだけです。 PG_NARGS() マクロと上に記載したメソッドは、この機能を使用するときに実際の引数とその型を決定するため使用されなければなりません。 また、こうした関数のユーザは、その関数呼び出しにおいて、関数が配列要素を分離した引数として扱うだろうという予想のもとで VARIADIC キーワードを良く使用するかもしれません。 関数自身は必要ならば、 get_fn_expr_variadic を実行した後で、実引数が VARIADIC 付きであることを検出した場合に、その動作を実装しなければなりません。

35.9.11. 変形関数

一部の関数呼び出しでは、関数固有の属性に基づいて計画作成を単純化できます。 例えば、 int4mul(n, 1) n だけに単純化することができます。 こうした関数固有の最適化を定義するためには、 変形関数 を作成し、そのOIDを主関数の pg_proc 項目の protransform フィールドに格納します。 変形関数は protransform(internal) RETURNS internal というSQLシグネチャを持たなければなりません。 引数、実際は FuncExpr * は、主関数の呼び出しを表すダミーノードです。 変形関数の式ツリー学習によって、式ツリーで表されるすべての可能性がある実際の呼び出しを単純化した式ツリーで置き換えることができることが証明された場合、単純化した式を構築し返します。 さもなければ、(SQLのNULLでは なく ) NULL ポインタを返します。

PostgreSQL が変形関数によって単純化できる場合に主関数を呼び出さないことは保証されません。 単純化した式と実際の主関数の呼び出しとで、厳密に等価であることを確実にしてください。

現在、セキュリティ上の懸念から、この機能がSQLレベルでユーザに見えることはありません。 このため、これは組み込み関数の最適化での使用のみで実用的です。

35.9.12. 共有メモリとLWLocks

アドインはLWLocks(軽量ロック)とサーバ起動時に共有メモリの割り当てを保持することができます。 shared_preload_libraries で指定して、こうしたアドインの共有ライブラリを事前にロードしなければなりません。 共有メモリは、その _PG_init 関数で以下を呼び出すことで保持されます。

void RequestAddinShmemSpace(int size)

LWLocksはその _PG_init 関数で以下を呼び出すことで保持されます。

void RequestAddinLWLocks(int n)

競合状態の可能性を防止するために、割り当てられた共有メモリへの接続やその初期化時に、以下のように各バックエンドで 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->mylockid = LWLockAssign();
        }
        LWLockRelease(AddinShmemInitLock);
}

35.9.13. 拡張へのC++の利用

以下のガイドラインに従うことで、 PostgreSQL の拡張を構築するためC++モードのコンパイラを利用できます。

  • バックエンドからアクセスされる関数はすべてバックエンドに対してCインタフェースを提供しなければなりません。 このC関数はC++関数を呼びだすことができます。 例えば、バックエンドからアクセスされる関数には extern C リンクが必要です。 これはバックエンドとC++コードの間でポインタとして渡される関数にも必要です。

  • 適切な解放メソッドを使ってメモリを解放してください。 例えば、ほとんどのバックエンドメモリは palloc() で確保されますので、 pfree() を使って解放してください。 この場合にC++の delete() を使うと失敗するでしょう。

  • 例外がCコードへ伝播しないようにしてください( extern C 関数すべての最上位ですべての例外を捕捉するブロックを使ってください)。 メモリ不足のようなイベントにより例外が発生する可能性がありますので、C++コードが何も例外を発生させない場合であっても、これは必要です。 例外はすべて捕捉しなければなりません。 そして適切なエラーをCインタフェースに渡してください。 可能であれば、例外を完全に除去できるように -fno-exceptions を付けてC++をコンパイルしてください。 その場合、例えば new() で返されるNULLの検査など、C++コード内で失敗の検査を行わなければなりません。

  • C++コードからバックエンド関数を呼び出す場合には、C++呼び出しスタック内にC言語互換構造体( POD )のみが含まれていることを確認してください。 バックエンドのエラーは、非PODオブジェクトを持つC++呼び出しスタックを適切に戻すことができない、長距離 longjmp() を生成しますので、これは必要です。

まとめると、バックエンドとやりとりするための壁の役割を担う extern C 関数の背後にC++コードを配置して、例外、メモリ、呼び出しスタックそれぞれの漏れを避けるのが最善です。


powered by SEO.CUG.NET