ユーザ定義の関数は C(もしくは、C++ のような Cと互換性のある言語) で作成することができます。そのような関数は動的ロード可能オブジェクト(共有ライブラリとも呼ばれます)としてコンパイルされ、必要に応じてサーバにロードされます。動的ロード機能とは、"C 言語"関数を"内部"関数と区別するものです。 コーディング方法は基本的に両方とも同じです。(従って、標準内部関数ライブラリは、ユーザ定義の C 関数のコーディング例の豊富な情報源となります。
現在、2つの 異なる呼び出し規約が C 関数で使用されています。より新しい "version 1" 呼び出し規約は、その関数用に呼び出しマクロ PG_FUNCTION_INFO_V1() を書くことで示されます。このマクロが存在しなければ、旧形式("version 0")の関数であることを示します。どちらの場合も CREATE FUNCTION で指定される言語名は C です。旧形式の関数は、移植性の問題と機能の不足のために、互換性の理由のためのみに現在存在しています。
特定のロード可能オブジェクト内のユーザ定義の関数がバックエンドセッションで最初に呼び出されると、動的ローダは、その関数を呼び出すことができるように、オブジェクトファイルをメモリ内に読み込みます。 そのため、ユーザ定義の C 関数用の CREATE FUNCTION はその関数について、ロード可能オブジェクトファイルの名前とオブジェクトファイル中内の呼び出される特定の関数の C 名称(リンクシンボル)2つの情報を指定しなければなりません。C 名称が明示的に指定されなかった場合、SQL における関数名と同じものと仮定されます。
CREATE FUNCTION コマンドで与えられた名前に基づいて、共有オブジェクトファイルの場所を見つける際に以下のアルゴリズムが使用されます。
名前が絶対パスの場合、指定されたファイルが読み込まれます。
名前が $libdir という文字列から始まる場合、その部分は PostgreSQL パッケージのライブラリディレクトリで置き換えられます。 このディレクトリはビルド時に決定されます。
名前にディレクトリ部分がない場合、そのファイルは dynamic_library_path 設定変数で指定されたパス 内から検索されます。
上記以外の場合(ファイルがパス内に存在しない場合や相対ディレクトリ部分をもつ場合)、動的ローダは指定された名前をそのまま使用し、ほとんどの場合は失敗します。(これは現在の作業ディレクトリに依存するため信頼できません。
ここまでの流れでうまくいかなかった場合、プラットホーム独自の共有ライブラリファイル拡張子 (多くの場合 .so) が指定された名前に追加され、再度、この流れを試みます。同様に失敗した場合は、読み込みは失敗します。
Note: PostgreSQL サーバの実効ユーザ ID はロード予定のファイルのパスまで到達できなければなりません。よくある失敗として postgres ユーザによって読み込み、実行、または両方の権限がそのファイルと、その上位ディレクトリに与えられていないことがあります。
どの場合でも、CREATE FUNCTION コマンドに与えたファイル名はそのままシステムカタログに保存されます。 もし、そのファイルを再度読み込む必要がある場合、同じ処理が適用されます。
Note: PostgreSQL はC 関数を自動的にコンパイルしません。CREATE FUNCTION コマンドで参照する前に、そのオブジェクトファイルはコンパイルされていなければなりません。さらなる情報については、Section 9.5.8 を参照して下さい。
Note: 初回の使用の後も、動的にロードされたオブジェクトはメモリ内に保持されます。同一セッションにおける、そのファイル内の関数の呼出しには、シンボルテーブルの検索に要する小さなオーバヘッドしかかかりません。そのオブジェクトを強制的に再読み込みさせる必要がある場合は、LOAD コマンドを使用するか新しいセッションを開始して下さい。
共有ライブラリを $libdir から相対的に、もしくは、動的ライブラリパスの通った所に配置することを推奨します。異なる位置に新しいインストレーションを配置する場合にバージョンアップを簡単にします。$libdir が示す実際のディレクトリは pg_config --pkglibdir コマンドを使用することで分かります。
Note: PostgreSQL リリース 7.2 以前では、CREATE FUNCTION で指定できるオブジェクトファイルは、正確な絶対パスだけでした。関数定義を不必要に移植できなくしていますので、この方法は古い方法とされています。パスや拡張子を付けずに共有ライブラリを単に指定し、検索機構によってその情報を提供させることが最善です。
Table 9-1 は、PostgreSQL にロードされるC言語関数の引数で必要とされるC言語の型の一覧です。"定義場所"列では、型定義を取り出すためにインクルードしなければならないヘッダファイルを示しています。(実際の定義は一覧中のファイルとは異なる可能性があります。ユーザは定義されたインタフェースを stick することを推奨されています。)postgres.h には必ず必要になる多くのものが宣言されていますので、ソースファイルの中で必ず始めにこのファイルをインクルードしなければならないことに注意して下さい。
Table 9-1. PostgreSQL の組み込み型と同等な C 言語型
SQL 型 | C 言語型 | 定義場所 |
---|---|---|
abstime | AbsoluteTime | utils/nabstime.h |
boolean | bool | postgres.h (コンパイラ組み込みの可能性があります) |
box | BOX* | utils/geo_decls.h |
bytea | bytea* | postgres.h |
"char" | char | (compiler built-in) |
character | BpChar* | postgres.h |
cid | CommandId | postgres.h |
date | DateADT | utils/date.h |
smallint (int2) | int2 or int16 | postgres.h |
int2vector | int2vector* | postgres.h |
integer (int4) | int4 or int32 | postgres.h |
real (float4) | float4* | postgres.h |
double precision (float8) | float8 * | postgres.h |
interval | Interval* | utils/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* | utils/timestamp.h |
tinterval | TimeInterval | utils/nabstime.h |
varchar | VarChar* | postgres.h |
xid | TransactionId | postgres.h |
PostgreSQL の内部では、基本型を「blob of memory(メモリの斑点)」とみなしています。ある型について定義したユーザー定義関数は、同様にPostgreSQLがその型をどのように実装するかを定義しています。つまり、PostgreSQLはデータをディスクに保存し、ディスクからデータを受取り、ユーザー定義関数でそのデータを入力値として受け取り、処理/出力するために使用します。基本型は下記の3つのいずれかの内部フォーマットを使用しています。
固定長の値渡し
固定長の参照渡し
可変長の値渡し
値渡しを使用する型は、1、2、4バイト長のも可能です(使用するマシンの sizeof(Datum) が 8 の場合は 8 バイトも可能です)。データ型を定義する際、その型がすべてのアーキテクチャにおいて同一の大きさ(バイト数)となるように定義することに注意してください。たとえば、long 型はマシンによっては 4 バイトであったり、8 バイトであったりして危険ですが、int 型はほとんどのUnixマシンでは4バイトです。Unix マシンにおける int4 の理論的な実装は以下のようになります。
/* 4 バイト整数、値渡し */ typedef int int4;
PostgreSQL は自動的にその整数型が本当にadvertise した大きさを持つように、figure out します。
一方、任意の大きさの固定長の型は参照として引き渡すことが可能です。下記の、PostgreSQLの型の実装サンプルを参照してください。
/* 16-バイト構造体、参照渡し */ typedef struct { double x, y; } Point;
それらの型のポインタのみがPostgreSQL関数の入出力時に使用できます。 それらの型の値を返すためには、palloc() を使用して正しい大きさのメモリ領域を割り当て、そのメモリ領域に値を入力し、それのポインタを返します(あるいは、ポインタを返すことによって同じ型の入力値を返すことも可能です。しかし、参照として渡すものの入力値の内容は決して変更しないでください)。
最後に、すべての可変長型は参照として引き渡す必要があります。また、すべての可変長型は正確に4バイトのlengthフィールドから始まる必要があり、その型に格納されるすべてのデータはlengthフィールドのすぐ後のメモリ領域に置かれる必要があります。lengthフィールドはその構造体の総長です(つまり、lengthフィールドそのものもその大きさに含まれます)。text型を定義するには、下記のように行えます。
typedef struct { int4 length; char data[1]; } text;
ここで宣言されたデータフィールドは、明らかにすべての取り得る文字列を保持できる長さではありません。C言語では可変長の構造体を定義することは不可能ですので、C コンパイラは配列の添字の範囲検査を行なわないという事実に依存します。もし正しい長さで宣言されたとすると、必要な領域量を割り当て、配列としてアクセスするだけです。(この手法に不慣れであるのならば、PostgreSQL サーバのプログラミングにより delve する前に C プログラムの入門書を読んで下さい。)可変長型を操作する時、正確な大きさのメモリを割当て、length フィールドを正確に設定することに注意する必要があります。例えば、40バイトを text 構造体に保持させたい場合、下記のようなコードを実行します。
#include "postgres.h" ... char buffer[40]; /* our source data */ ... text *destination = (text *) palloc(VARHDRSZ + 40); destination->length = VARHDRSZ + 40; memcpy(destination->data, buffer, 40); ...
VARHDRSZ は sizeof(int4) と同一ですが、可変長型のオーバヘッド分の大きさを参照する時には、VARHDRSZ マクロを使用する方が好ましい形式とみなされています。
これで基本型用のすべてのあり得る構造体についての説明が完了しましたので、実際の関数の例をいくつか示します。
まず最初に、現在は非推奨ですが理解しやすいので、"古いスタイル"の呼び出し規約を記述します。Version-0メソッドでは、C言語関数の引数と結果は、通常の C のプログラムの記述の方法と同じような形式で行いますが、上記の説明のように、各 SQLのデータ型を示すC言語を使用には注意してください。
以下にいくつか例を示します。
#include "postgres.h" #include <string.h> /* 値渡し */ 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)); VARATT_SIZEP(new_t) = VARSIZE(t); /* * VARDATA は構造体のデータ領域へのポインタです。 */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t)-VARHDRSZ); /* how many bytes */ 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); VARATT_SIZEP(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(int4) RETURNS int4 AS 'PGROOT/tutorial/funcs' LANGUAGE C WITH (isStrict); -- add_one() SQL 関数名をオーバライドしていることに注意 CREATE FUNCTION add_one(float8) RETURNS float8 AS 'PGROOT/tutorial/funcs', 'add_one_float8' LANGUAGE C WITH (isStrict); CREATE FUNCTION makepoint(point, point) RETURNS point AS 'PGROOT/tutorial/funcs' LANGUAGE C WITH (isStrict); CREATE FUNCTION copytext(text) RETURNS text AS 'PGROOT/tutorial/funcs' LANGUAGE C WITH (isStrict); CREATE FUNCTION concat_text(text, text) RETURNS text AS 'PGROOT/tutorial/funcs' LANGUAGE C WITH (isStrict);
ここで、PGROOT は PostgreSQL のソースツリーの絶対パスを意味します。AS 句中では単に 'funcs' を使用し、後で PGROOT/tutorial を検索パスに追加する方がより良い方法です。どの場合でも、.so や .sl が一般的に使用される、共有ライブラリ用のシステム独特の拡張子を省略することができます。
ここで、関数を"厳密(strict)"と指定していることに注目してください。 それは、もし入力された値がNULLであった場合に、システムが自動的に返り値もNULLであるとみなすことを意味します。これを行うことによって、関数のコードで入力値がNULLであるかどうかのチェックを行う必要がなくなります。 これがなければ、各参照渡し引数の NULL ポインタについてのチェックを行うなど、NULL 値の明示的なチェックを行う必要性が出てきます (値渡し引数に関しては、チェックを行う方法は存在しません)。
これらの呼び出し規約は容易ですが、この方法は、int型のより小さいデータ型を引き渡す部分で問題を抱えているアーキテクチャでは、移植性の面ではあまり優れていません。また、関数の結果としてNULLを返す簡単な方法はありません。 その上、引数としてNULLを処理する方法としては、関数を精密なものにする以外方法はありません。次に説明のあるVersion-1の規約ではこれらの問題が解決されています。
Version-1の呼び出し規約では、引数と結果の引き渡しの複雑さをなくすためにマクロを使用しています。Version-1関数のC言語宣言は必ず下記のように行います。
Datum funcname(PG_FUNCTION_ARGS)
さらに、マクロ呼び出し
PG_FUNCTION_INFO_V1(funcname);
は、同じソースファイルに書かれている必要があります (一般には、関数の直前に書かれます)。現在 PostgreSQL ではすべての内部関数は Version-1 であると認識するので、このマクロの呼び出しは 内部言語関数では必要ありません。 しかし、動的にロードされる関数では 必要です。
Version-1 関数では、それぞれの実際の引数は、引数の型に合った PG_GETARG_xxx() マクロを使用して取り出され、結果は戻り値の型に合った PG_RETURN_xxx() マクロを使用して返されます。
上記と同じ関数をVersion-1形式で記述したものを以下に示します。
#include "postgres.h" #include <string.h> #include "fmgr.h" /* 値渡し */ 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)); VARATT_SIZEP(new_t) = VARSIZE(t); /* * VARDATA は構造体のデータ領域へのポインタです。 */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t)-VARHDRSZ); /* how many bytes */ 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); VARATT_SIZEP(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 マクロは"toasted" 値(圧縮、または行外)の取り扱う必要性を隠蔽します。 上記にある、古いスタイルの copytext 関数とconcat_text 関数は、toasted値が存在した場合、入力にpg_detoast_datum()を使用しないので、実際は誤ったものとなります(古いスタイルの動的にロードされる関数のハンドラは、現在、この問題に対処していますが、Version-1関数で実行可能なことよりも劣ります)。
Version-1関数の1つの大きな改善点は、NULLの入力/結果の処理能力です。PG_ARGISNULL(n) マクロにより関数は各入力がNULLであるかどうかのテストを行なうことができます(これは、"厳密"と宣言されていない関数でのみ必要です)。PG_GETARG_xxx()マクロでは、入力された引数は始まり値を0として数え上げられます。引数が 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 を使用して指定できます。 ストレージの型は、plain、external、extended、または main のいずれかです。)
Version-1 関数呼び出し規約では、"セット(set)" の結果を返すこと、および、トリガ関数と手続型言語の呼び出しハンドラを実装することができます。また、Version-1 コードは、ANSI Cの制約が関数呼び出しプロトコルで守られているので、Version-0よりも移植性があります。詳細はソースディストリビューションのsrc/backend/utils/fmgr/READMEを参照してください。
複合型では、Cの構造体のような固定のレイアウトがありません。複合型のインスタンスはヌルフィールドを持つ可能性があります。更に、複合型で、継承階層の一部であるものは同じ継承の階層の他のメンバとは異なるフィールドを持つ可能性もあります。そのため、PostgreSQLはC言語から複合型のフィールドにアクセスするための手続きのインターフェイスを提供します。 PostgreSQLが行の集合を処理するにつれ、各行は不明瞭なTUPLE型の構造体として引き渡されます。問い合わせを答える以下のような関数を書こうとしていると仮定します。
SELECT name, c_overpaid(emp, 1500) AS overpaid FROM emp WHERE name = 'Bill' OR name = 'Sam';
上記の問い合わせでは、c_overpaid を下記のように定義することができます。
#include "postgres.h" #include "executor/executor.h" /* for GetAttributeByName() */ bool c_overpaid(TupleTableSlot *t, /* the current row of EMP */ int32 limit) { bool isnull; int32 salary; salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull)); if (isnull) return (false); return salary > limit; } /* version-1 コーディングでは、上の関数は以下のようになります。 */ PG_FUNCTION_INFO_V1(c_overpaid); Datum c_overpaid(PG_FUNCTION_ARGS) { TupleTableSlot *t = (TupleTableSlot *) PG_GETARG_POINTER(0); int32 limit = PG_GETARG_INT32(1); bool isnull; int32 salary; salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull)); if (isnull) PG_RETURN_BOOL(false); /* 給与が空の場合は、代わりに、 PG_RETURN_NULL() を行なうように変更する方がいいかも知れません */ PG_RETURN_BOOL(salary > limit); }
GetAttributeByNameは、現在の行から属性を返す、PostgreSQLシステム関数です。これには3つの引数があります。それらはTupleTableSlot*型が関数に引き渡されたときの引数、求められた属性の名前、属性がNULLであるかどうかの返りパラメータです。GetAttributeByName は適切な DatumGetXXX() マクロを使用して適切なデータ型に変換可能な Datum 型の値を返します。
下記のコマンドは、PostgreSQL に c_overpaid 関数を認識させるものです。
CREATE FUNCTION c_overpaid(emp, int4) RETURNS bool AS 'PGROOT/tutorial/funcs' LANGUAGE C;
テーブル関数 API はユーザ定義の C 言語テーブル関数を作成する際の補助として使用します (Section 9.7)。 テーブル関数は、行のセットを生成する関数で、基本 (スカラ) データ型あるいは複合 (複数列) データ型のどちらかです。 この API は、複合データ型を返すためのサポートと複数行を返すためのサポート (set returning functions、すなわち SRF) という 2 つの主要なコンポーネントに分かれます。
テーブル関数 API はマクロと関数を使用して、複合データ型をビルドして複数の結果を返す際の複雑さを取り除きます。 テーブル関数は、先に説明した version-1 呼び出し規約の従わなくてはなりません。 さらに、ソースファイルには以下が含まれている必要があります。
#include "funcapi.h"
テーブル関数 API では AttInMetadata 構造で始まる複合データ型 (または行) を返すことができます。 この構造は、未加工の C 文字列から行を作成するのに必要な個別の属性情報の配列を持っています。 またこの構造は、ポインタ を TupleDesc に保存します。 この構造体に保持されている情報は TupleDesc から派生したものですが、テーブル関数の呼び出し毎の CPU サイクルを節約するためにここに保存されています。 関数が set を返す場合、AttInMetadata 構造は最初の呼び出しの際に 1 度計算され、後の呼び出しで再利用するために保存される必要があります。
typedef struct AttInMetadata { /* TupleDesc がいっぱい */ TupleDesc tupdesc; /* 属性の型の入力関数の配列 finfo */ FmgrInfo *attinfuncs; /* 属性の型の配列 typelem */ Oid *attelems; /* 属性の配列 typmod */ int32 *atttypmods; } AttInMetadata;
この構造を移植するために使用できる関数およびマクロがいくつか用意されています。 例えば、
TupleDesc RelationNameGetTupleDesc(const char *relname)
を使用して、指定されたリレーションに基づいて TupleDesc を取得することができます。 また、
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
を使用して、型の OID に基づいて TupleDesc を取得することができます。 これは基本 (スカラ) 型または複合 (リレーション) 型のどちらの TupleDesc を取得する場合にも使用できます。 そして、
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
で、指定されたTupleDesc に基づいて初期化された AttInMetadata へのポインタが返されます。 AttInMetadata を C 文字列とともに使用して正しく形成されたタプルを作成することができます。 複数の呼び出しによる作業の重複を避けるため、ここにメタデータが保存されています。
タプルを返すには、TupleDesc に基づいてタプルスロットを作成する必要があります。 @例えば、
TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc)
を使用してタプルスロットを初期化することもできますし、ユーザ独自の他の方法でタプルスロットを入手することもできます。 タプルスロットは、関数によって返される Datum の作成に必要です。 スロットは呼び出しの度に再利用するようにしてください。
AttInMetadata 構造を作成したら、
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
を使用して、C 文字列形式でユーザデータを含む HeapTuple を作成します。 "values" は C 文字列の配列で、返されるタプルの各属性につき 1 つ必要です。 それぞれの C 文字列は、属性データ型の入力関数が予定する形式でなければなりません。 ある属性について NULL 値を返すには、values 配列の対応するポインタが NULL に設定されている必要があります。 この関数はタプルを返す度に呼び出す必要があります。
TupleDescGetAttInMetadata および BuildTupleFromCStrings を使用してタプルを作成する方法は、ユーザの関数が返される値を自動的にテキスト文字列として計算する場合にのみ有用です。 自動的に値を Datum のセットとして計算する場合は、代わりに基礎的な heap_formtuple ルーチンを使用して Datum を直接タプルに変換する方法を使用してください。 その場合でも TupleDesc と TupleTableSlot は必要ですが、AttInMetadata は必要ありません。
関数で返すタプルを作成したら、そのタプルを Datum に変換する必要があります。 それには、
TupleGetDatum(TupleTableSlot *slot, HeapTuple tuple)
を使用してタプルとスロットを含む Datum を取得します。 この Datum は、1 行のみを返す場合には直接返すことができます。また、セットを返す関数の現在の結果値として使用することもできます。
以下に例を示します。
セットを返す関数 (set-returning function、SRF) は通常、返される項目ごとに呼び出されます。 そのため、SRF は、過去の操作を記憶して呼び出しの度に次の項目を返すために十分な状態を保ってる必要があります。 テーブル関数 API には、この処理を制御するための 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; /* * 省略可能 : 結果スロットへのポインタ * * スロットはタプルを返す際 (たとえば複合データ型) に使用され、 * 基本 (スカラ) データ型を返す場合には必要ありません。 */ 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; } 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; MemoryContext oldcontext; [user defined declarations] if (SRF_IS_FIRSTCALL()) { funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* 1 度限りのセットアップコードがここに入ります: */ [user defined code] [if returning composite] [build TupleDesc, and perhaps AttInMetadata] [obtain slot] funcctx->slot = slot; [endif returning composite] [user defined code] MemoryContextSwitchTo(oldcontext); } /* 毎回実行するセットアップコードがここに入ります: */ [user defined code] funcctx = SRF_PERCALL_SETUP(); [user defined code] /* これは、終了したかどうかをテストする方法の 1 つです: */ if (funcctx->call_cntr < funcctx->max_calls) { /* ここで、別の項目を返します: */ [user defined code] [obtain result Datum] SRF_RETURN_NEXT(funcctx, result); } else { /* これで項目を返し終わりました。 あとはクリーンアップするだけです。 */ [user defined code] SRF_RETURN_DONE(funcctx); } }
複合型を返す単純な SRF の完全な例は以下のとおりです。
PG_FUNCTION_INFO_V1(testpassbyval); Datum testpassbyval(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tupdesc; TupleTableSlot *slot; 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); /* * a__testpassbyval タプル用のタプル記述を作成 */ tupdesc = RelationNameGetTupleDesc("__testpassbyval"); /* この tupdesc を持つタプルへのスロットの割り当て */ slot = TupleDescGetSlot(tupdesc); /* 関数コンテキストへのスロットの割り当て */ funcctx->slot = slot; /* * 後で未加工の C 文字列からタプルを作成するために必要となる * 属性メタデータの生成 */ attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; MemoryContextSwitchTo(oldcontext); } /* すべての関数呼び出しで実行 */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; slot = funcctx->slot; attinmeta = funcctx->attinmeta; if (call_cntr < max_calls) /* 他にも送るものがある場合 */ { char **values; HeapTuple tuple; Datum result; /* * スロット内のストレージ用に values 配列を準備。 * これは、後で適切な "in" 関数で処理される * 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 = TupleGetDatum(slot, 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 __testpassbyval AS (f1 int4, f2 int4, f3 int4); CREATE OR REPLACE FUNCTION testpassbyval(int4, int4) RETURNS setof __testpassbyval AS 'MODULE_PATHNAME','testpassbyval' LANGUAGE 'c' IMMUTABLE STRICT;
テーブル関数についての詳細は、contrib/tablefunc を参照してください。
ここからは、より難易度の高い、プログラミング言語関数の説明となります。 注意してほしいのは、この節の主旨は、プログラマを養成することではないということです。 PostgreSQL 用の C 関数を書く前に、C言語についてよく理解している必要があります(ポインタの使用も含みます)。C言語以外の言語で記述した関数をPostgreSQLに組み込みむことは可能ですが、たとえばFORTRANやPascalといった言語はC言語と同じ呼び出し規約ではないので、多くの場合、(可能であったとしても)困難です。それはつまり、他の言語では同じ方法で引数を渡したり結果を返すことを行わないということです。このため、プログラミング言語関数はC言語で書かれているものと仮定します。
C関数を作成するときの基本的な規則は下記のものです。
pg_config --includedir-server を使用して、使用中のシステム(もしくはユーザが実行するシステム)にてPostgreSQL サーバのヘッダファイルがインストールされた場所を見つけます。 このオプションは、PostgreSQL 7.2 で新しく導入された機能です。 PostgreSQL 7.1 では --includedir を使う必要がありました。(pg_config は認識できないオプションが与えられた時に非 0 のステータスで終了します。)7.1 より前のリリースでは、推測する必要がありますが、現在の呼出し規約が導入される前のものですので、これらのリリースをサポートしようとは考えないでしょう。
メモリを割り当てる際、Cライブラリのmallocとfreeではなく、PostgreSQLのpallocとpfreeを使用してください。pallocで割り当てられたメモリは各トランザクションの終わりに自動的に解放され、メモリリークを防ぎます。
memsetまたはbzeroを使用して、構造体を必ず0クリアにしてください。複数のルーチン(ハッシュアクセスメソッド、ハッシュ結合、ソートアルゴリズム)は構造体に含まれている生のビットの関数を計算します。構造体のすべてのフィールドを初期化しても必要のない値を持っている、数バイトの位置揃え用のパディング(構造体内の穴)が存在する可能性があります。
ほとんどのPostgreSQLの内部型は postgres.hに宣言されています。 一方、関数管理インターフェイス(PG_FUNCTION_ARGSなど)はfmgr.hで宣言されています。 したがって、少なくともこの2つのファイルをインクルードする必要があります。移植性に関する理由により、postgres.h をその他のシステムヘッダファイル、ユーザヘッダファイルよりも先にインクルードしておくことが最善です。postgres.hをインクルードすることは elog.h、palloc.hもインクルードすることになります。
オブジェクトファイルで定義されているシンボル名は互いに、および、PostgreSQLサーバの実行ファイルで定義されているものと異なっている必要があります。これに関するエラーが表示される場合は、関数名または変数を変更する必要があります。
PostgreSQLに動的にロードできるようにオブジェクトコードをコンパイル/リンクするときには、特別なフラグが必要となります。その方法を特有のオペレーティングシステムで行う場合の詳細は Section 9.5.8を参照してください。
C で書かれた PostgreSQL の拡張関数を使うためには、サーバが動的にロードできるように特別な方法でコンパイルとリンクを行う必要があります。正確には 共有ライブラリを作る必要があります。
詳しい情報はオペレーティングシステムのドキュメント、特に C コンパイラ cc と、リンクエディタ ld のマニュアルページを参照してください。さらに、PostgreSQL のソースコードの contrib ディレクトリにいくつか実例があります。しかしもしこれらの例に頼ると PostgreSQL ソースコードの有効性に依存したモジュールが作られてしまいます。
共有ライブラリの作成は実行プログラムのリンクと似ています。まずソースファイルがオブジェクトファイルにコンパイルされ、そのオブジェクトファイル同士がリンクされます。これらのオブジェクトファイルは位置独立なコード(PIC)として作られる必要があります。 それは概念的には、実行プログラムから呼び出されるときにメモリの適当な場所に置くことができるということです。(実行プログラムとして作られたオブジェクトファイルはそのようにはコンパイルされません。)共有ライブラリをリンクするコマンドは実行プログラムのリンクと区別するための特別なフラグがあります。少なくとも理論上ではそのようになっています。他のシステムではもっと醜い実際が見受けられます。
次の例ではソースコードはファイル foo.c にあると仮定し、foo.so という共有ライブラリを作るとします。中間のオブジェクトファイルは特別な記述がない限り foo.o と呼ばれます。共有ライブラリは 1 つ以上のオブジェクトファイルを持つことができますが、ここでは 1 つしか使いません。
PIC を作るためのコンパイラフラグは -fpic です。共有ライブラリを作るリンカフラグは -shared です。
gcc -fpic -c foo.c ld -shared -o foo.so foo.o
これは BSD/OS のバージョン 4.0 に適用されます。
PIC を作るためのコンパイラフラグは -fpic です。共有ライブラリを作るリンカフラグは -sharedです。
gcc -fpic -c foo.c gcc -shared -o foo.so foo.o
これは FreeBSD のバージョン 3.0 に適用されます。
PIC を作るためのシステムコンパイラのコンパイラフラグは +z です。GCC を使う場合は-fpic です。共有ライブラリのためのリンカフラグは -b です。以下の 3 つの方法が考えられます。
cc +z -c foo.c
または
gcc -fpic -c foo.c
and then
ld -b -o foo.sl foo.o
HP-UX は他のほとんどのシステムと異なり共有ライブラリに .sl という拡張子を使います。
PIC がデフォルトで、特別なコンパイラオプションは何も必要ありません。共有ライブラリを作るためのリンカオプションは -sharedです。
cc -c foo.c ld -shared -o foo.so foo.o
PIC を作るためのコンパイラフラグは -fpic です。いくつかのプラットフォームでは状況によって -fpic が動作しない場合は -fPIC を使わなければいけません。さらに詳しい情報については GCC のマニュアルを参照してください。共有ライブラリを作るコンパイラフラグは -shared です。完全な例は下記のようになります。
cc -fpic -c foo.c cc -shared -o foo.so foo.o
例を以下に示します。 開発者用のツールがインストールされていることが前提です。
cc -c foo.c cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
PIC を作るためのコンパイラフラグは -fpic です。ELF システムではフラグ -shared がついたコンパイラが共有ライブラリをリンクするために使われます。より古い非 ELF システムでは ld -Bshareable が使われます。
gcc -fpic -c foo.c gcc -shared -o foo.so foo.o
PIC を作成するためのコンパイラフラグは -fpic です。共有ライブラリをリンクするには ld -Bshareable を使用します。
gcc -fpic -c foo.c ld -Bshareable -o foo.so foo.o
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
PIC はデフォルトで、コンパイルコマンドは通常のものです。 リンクのためには特別なオプション付の ld を使用します。
cc -c foo.c ld -shared -expect_unresolved '*' -o foo.so foo.o
システムのコンパイラではなく GCC を使う場合の手順は、特別のオプションは必要ありません。
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
Tip: もし拡張モジュールをより広く配布するためのパッケージにしたい場合、GNU Libtool を共有ライブラリの構築に使うことを検討するべきです。これはプラットフォームの違いを、一般的で強力なインターフェイスにカプセル化します。真剣なパッケージ作成はさらにライブラリバージョン管理、シンボル解決メソッドなどの検討も必要になります。
これで完成した共有ライブラリファイルは Postgres にロードすることができます。CREATE FUNCTION コマンドにファイル名を指定するときには、中間オブジェクトファイルではなく共有ライブラリファイル名(の名前を与えてください。システムの標準共有ライブラリ拡張(通常 .so あるいは .sl で終わるもの)は CREATE FUNCTION で省略することができ、そして移植性を最も高くするため通常は省略されます。
サーバがライブラリファイルをどこに見つけるかに関しては Section 9.5.1 を見直してください。