ユーザ定義の関数は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)が指定された名前に追加され、再度この流れを試みます。 同様に失敗した場合は、読み込みは失敗します。
共有ライブラリを$libdirから相対的に、もしくは動的ライブラリパスの通った所に配置することを推奨します。 異なる場所に新しいインストレーションを配置する場合にバージョンアップを簡単にします。 $libdirが示す実際のディレクトリはpg_config --pkglibdirコマンドを使用することでわかります。
PostgreSQLサーバの実効ユーザIDはロード予定のファイルのパスまで到達できなければなりません。 よくある失敗として、postgresユーザに対して読み込み、実行、または両方の権限がそのファイルとその上位ディレクトリに与えられていないことがあります。
どの場合でも、CREATE FUNCTIONコマンドに与えたファイル名はそのままシステムカタログに保存されます。 ですので、もしそのファイルを再度読み込む必要がある場合、同じ処理が適用されます。
注意: PostgreSQLはC関数を自動的にコンパイルしません。 CREATE FUNCTIONコマンドで参照する前に、そのオブジェクトファイルはコンパイルされていなければなりません。 さらなる情報については、項34.9.6を参照してください。
確実に、動的にロードされるモジュールが互換性がないサーバにロードされないように、PostgreSQLは、そのファイルに適切な内容を持つ"魔法のブロック"が含まれているかどうか検査します。 これによりサーバは、メジャーバージョンが異なるPostgreSQL用にコンパイルされたモジュールなど、明確に互換性がないことを検知することができます。 魔法のブロックはPostgreSQL 8.2から要求されています。 魔法のブロックをincludeさせるには、以下をモジュールのソースファイルに一度(一度だけ)、fmgr.hヘッダファイルをincludeさせた後で、記述してください。
#ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif
そのコードをリリース8.2より前のPostgreSQL用にコンパイルする必要がなければ、#ifdefテストを省略することができます。
最初に使用された後も、動的にロードされたオブジェクトはメモリ内に保持されます。 同一セッションにおいてそのファイル内の関数をその後に呼び出した場合、シンボルテーブルの検索に要する小さなオーバーヘッドしかかかりません。 例えば再コンパイルした後など、そのオブジェクトファイルを強制的に再度読み込ませる必要がある場合は、LOADコマンドを使用するか新しいセッションを開始してください。
省略することもできますが、動的にロードされるファイルに初期化処理関数と最終処理関数を含めることができます。
_PG_init
という関数がファイルに存在すると、この関数はファイルがロードされた直後に呼び出されます。
_PG_fini
という関数がファイルに存在すると、この関数はファイルがアンロードされる直前に呼び出されます。
また、この関数は引数を取らずvoid型を返さなければなりません。
_PG_fini
がファイルのアンロード時にのみ呼び出されるものであり、処理の終了時に呼び出されるものではないことに注意してください。
(現在、アンロードは明示的なLOADコマンドによるファイル再ロードのコンテキストでのみ発生します。)
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の型の実装サンプルを示します。
/* 16 バイト構造体、参照渡し */ typedef struct { double x, y; } Point;
それらの型のポインタのみがPostgreSQL関数の入出力時に使用できます。 それらの型の値を返すためには、palloc()を使用して正しい大きさのメモリ領域を割り当て、そのメモリ領域に値を入力し、それのポインタを返します (また、入力引数の1つと同じ型かつ同じ値を返したいのであれば、pallocを行う手間を省くことができます。この場合は入力値へのポインタを単に返してください)。
最後に、全ての可変長型は参照として引き渡す必要があります。 また、全ての可変長型は正確に4バイトのlengthフィールドから始まる必要があり、その型に格納される全てのデータはlengthフィールドのすぐ後のメモリ領域に置かれる必要があります。 lengthフィールドにはその構造体の総長が格納されます。つまり、lengthフィールドそのものもその大きさに含まれます。
警告 |
参照渡しの入力値の内容を決して変更しないでください。 指定したポインタがディスクバッファを直接指し示している可能性がよくありますので、変更すると、ディスク上のデータを破壊してしまうかもしれません。 この規則の唯一の例外について項34.10で説明します。 |
例えば、text型を定義するには、下記のように行えます。
typedef struct { int4 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); destination->length = VARHDRSZ + 40; memcpy(destination->data, buffer, 40); ...
VARHDRSZはsizeof(int4)と同一ですが、可変長型のオーバーヘッド分の大きさを参照する時には、VARHDRSZマクロを使用する方が好ましい形式とみなされています。
表34-1は、PostgreSQLの組み込み型を使用するC言語関数を作成する時の、Cの型とSQL型との対応を規定したものです。 "定義場所"列では、型定義を取り出すためにインクルードしなければならないヘッダファイルを示しています (実際の定義は一覧中のファイルとは異なる可能性があります。 ユーザは定義されたインタフェースを厳守することを推奨されています)。 postgres.hには必ず必要になる多くのものが宣言されていますので、ソースファイルの中で必ず初めにこのファイルをインクルードしなければならないことに注意してください。
表 34-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) | 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 |
ここまでで基本型に関してあり得る構造体の全てを記述しましたので、実際の関数の例をいくつか示すことができます。
まず最初に、現在は非推奨ですが理解しやすいので、"古いスタイル"の呼び出し規約を記述します。 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)); SET_VARSIZE(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); 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ポインタについてのチェックを行うなど、NULL値の明示的なチェックを行う必要性が出てきます (値渡し引数に関しては、チェックを行う方法は存在しません)。
これらの呼び出し規約は容易ですが、この方法は、移植性の面であまり優れていません。int型のより小さいデータ型を引き渡す部分で問題を抱えているアーキテクチャも存在します。 また、関数の結果としてNULLを返す簡単な方法はありません。 その上、引数としてNULLを処理する方法としては、関数を厳密なものにする以外方法はありません。 次に説明する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" /* 値渡し */ 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), /* 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); 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()
マクロでは、入力された引数は始まり値を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関数呼び出し規約では、結果集合(項34.9.10)を返すこと、およびトリガ関数(第35章)と手続型言語の呼び出しハンドラ(第48章)を実装することができます。 また、Version-1コードは、標準Cの関数呼び出しプロトコルの制約を守りますので、Version-0よりも移植性があります。 詳細はソース配布物内のsrc/backend/utils/fmgr/READMEを参照してください。
より先進的な話題に入る前に、PostgreSQL C言語関数のコーディングについての規則をいくつか説明します。 C言語以外の言語で記述した関数をPostgreSQLに組み込みむことは可能ですが、例えばC++、FORTRANやPascalといった言語はC言語と同じ呼び出し規約に従いませんので、多くの場合、(可能であったとしても)困難です。 それはつまり、他の言語では同じ方法で関数に引数を渡したり、関数から結果を返すことを行わないということです。 このため、C言語関数は実際にC言語で書かれているものと仮定します。
C関数の作成と構築の基本規則を以下に示します。
pg_config --includedir-serverを使用して、使用中のシステム(もしくはユーザが実行するシステム)にてPostgreSQLサーバのヘッダファイルがインストールされた場所を見つけます。
PostgreSQLに動的にロードできるように独自コードをコンパイル/リンクする時には常に、特別なフラグが必要となります。 特定のオペレーティングシステムにおけるコンパイル/リンク方法については項34.9.6を参照してください。
忘れずに項34.9.1で説明した"魔法のブロック"を共有ライブラリで定義してください。
メモリを割り当てる際、Cライブラリのmalloc
とfree
ではなく、PostgreSQLのpalloc
とpfree
を使用してください。
palloc
で割り当てられたメモリは各トランザクションの終わりに自動的に解放され、メモリリークを防ぎます。
memset
を使用して、構造体を必ず0クリアにしてください。
こうしないと、ハッシュインデックスやハッシュ結合をサポートすることが困難です。
ハッシュを計算するには、データ構造体内の有意なビットのみを取り出す必要があるためです。
構造体の全てのフィールドを初期化したとしても、不要な値を持つ、位置揃え用のパディング(構造体内の穴)が存在する可能性があります。
ほとんどのPostgreSQLの内部型はpostgres.hに宣言されています。 一方、関数管理インタフェース(PG_FUNCTION_ARGSなど)はfmgr.hで宣言されています。 したがって、少なくともこの2つのファイルをインクルードする必要があります。 移植性に関する理由により、postgres.hをその他のシステムヘッダファイル、ユーザヘッダファイルよりも先にインクルードしておくことが最善です。 postgres.hをインクルードすることはelog.h、palloc.hもインクルードすることになります。
オブジェクトファイルで定義されているシンボル名は互いに、およびPostgreSQLサーバの実行ファイルで定義されているものと異なっている必要があります。 これに関するエラーが表示される場合は、関数名または変数を変更する必要があります。
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です。 したがって、以下のようになります。
cc +z -c foo.c
または
gcc -fpic -c foo.c
そして
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
ティップ: これがあまりに難しいようであれば、GNU Libtoolの使用を検討すべきです。 これはプラットフォームの違いを、統一されたインタフェースで判らないようにします。
これで完成した共有ライブラリファイルはPostgreSQLにロードすることができます。 CREATE FUNCTIONコマンドにファイル名を指定する時には、中間オブジェクトファイルではなく共有ライブラリファイル名の名前を与えてください。 システムの標準共有ライブラリ用の拡張子(通常.soあるいは.sl)はCREATE FUNCTIONで省略することができ、そして移植性を最も高くするため通常は省略されます。
サーバがライブラリファイルをどこに見つけるかに関しては項34.9.1を見直してください。
PostgreSQL拡張モジュールの配布を予定している場合、構築システムを移植可能にするための設定はかなり困難です。 そのため、PostgreSQLのインストレーションでは、単純な拡張モジュールをインストール済みのサーバに簡単に構築できるようにPGXSという拡張用の構築基盤を提供しています。 この基盤は、PostgreSQLとやり取りする全てのソフトウェアを構築するために使用できるような総合的な構築システムフレームワークを目的としたものではないことに注意してください。 これは単に単純なサーバ拡張モジュールで共通する構築用手順を自動化するものです。 もっと複雑なパッケージでは、独自に構築システムを作成する必要があります。
独自の拡張モジュール用にこの基盤を使用するには、簡単なMakefileを作成しなければなりません。 このMakefileで、いくつかの変数を設定し、最後にPGXS全体用のMakefileをインクルードさせなければなりません。 ここでは、isbn_issnという名前の拡張モジュールを構築するための例を示します。 このモジュールは共有ライブラリ、SQLスクリプト、および文書用のテキストファイルから構成されています。
MODULES = isbn_issn DATA_built = isbn_issn.sql DOCS = README.isbn_issn PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS)
最後の3行は常に同じなはずです。 その前に、変数の代入や独自のmake規則の追加を行います。
以下の変数を設定することができます。
同じ系統のソースファイルから構築される共有オブジェクトのリストです (このリストには拡張子を含めないでください)。
prefix/share/contribにインストールされる様々なファイルです。
最初に構築されなければならない、prefix/share/contribにインストールされる様々なファイルです。
prefix/doc/contribにインストールされる様々なファイルです。
prefix/binにインストールされる(非バイナリの)スクリプトファイルです。
最初に構築されなければならない、prefix/binにインストールされる(非バイナリの)スクリプトファイルです。
リグレッションテストケースの(拡張子なしの)リストです。 後述します。
もしくは、以下の2つのうち、多くて1つです。
(OBJSのオブジェクトファイルのリストから)構築されるバイナリプログラムです。
(OBJSのオブジェクトファイルのリストから)構築される共有オブジェクトです。
以下の変数も設定することができます。
make cleanで削除される余計なファイルです。
これはCPPFLAGSに追加されます。
これはPROGRAMのリンク行に追加されます。
これはMODULE_bigのリンク行に追加されます。
PostgreSQLインストレーションに対して構築されたpg_configプログラムのパスです。 (通常単に、使用中のPATHにおいて最初に使われるpg_configです。)
このMakefileを拡張モジュールを格納するディレクトリにMakefileとして保存してください。 その後、makeで独自モジュールのコンパイルを、make installで独自モジュールのインストールを行うことができます。 デフォルトで拡張モジュールは、検索パス内で最初に見つかるpg_configコマンドに対応するPostgreSQLインストレーション向けにコンパイルされ、インストールされます。 Makefile内またはmakeのコマンドライン上でPG_CONFIGを別のインストレーションのpg_configプログラムを指し示すように変更することで別のインストレーションを使用することができます。
注意 |
PG_CONFIGの変更は、PostgreSQL 8.3以降に対して構築する場合にのみ動作します。 これより古いリリースでは、pg_config以外の何かに設定しても動作しません。 構築対象のインストレーションを選択するためにはPATHを変えなければなりません。 |
PostgreSQL 主サーバで使用されるmake installcheck とまったく同様に、REGRESS変数内に列挙されたスクリプトが独自のモジュールのリグレッションテスト用に使用されます。 これが動作するためには、独自拡張のディレクトリ内にsql/という名称のディレクトリを用意し、その中に実行させたい試験グループそれぞれに1つのファイルを格納しなければなりません。 このファイルの拡張子は.sqlでなければなりません。また、makefileファイル内のREGRESSリストにはこの拡張子を含めてはいけません。 それぞれの試験に関して、expected/という名称のディレクトリに、.outという拡張子で想定結果をファイルとして格納します。 試験はmake installcheckによって実行され、その結果は想定値ファイルと比較されます。 相違点は diff -cの書式でregression.diffsというファイルに書き出されます。 想定値ファイルが存在しない試験を実行すると"障害"として報告されますので、想定値ファイルがすべて存在するか確認してください。
ティップ: 想定値ファイルの最も簡単な作成方法は空のファイルを作成し、試験を実行した後の結果ファイル(results/ディレクトリにあります。)を、それが正に試験結果として正しいものかどうか注意深く検査した後にexpected/にコピーすることです。
複合型では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()用 */ 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()用 */ 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
は適切な DatumGetXXX()
マクロを使用して適切なデータ型に変換可能な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を使用していることに注意してください。
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)
(これにはもちろん、バージョン1呼び出し規約を使用していることが必要です)。 resultTypeIdをNULLとすることも、ローカル変数のアドレスを指定して関数の戻り値型を受け取ることができます。 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を直接返すことができます。 さもなくば、集合を返す関数における現在の戻り値として使用することができます。
次節に例を示します。
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; MemoryContext oldcontext; further declarations as needed if (SRF_IS_FIRSTCALL()) { 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ディレクトリには、集合を返す関数のより多くの例があります。
C言語関数は、anyelement、anyarray、anynonarrayおよびanyenum多様型を受け付ける、または返すように宣言することができます。
多様関数の詳細な説明は項34.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
を使用することもできます。
例えば、任意の型の単一要素を受け付け、その型の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;
アドインは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 (!ptr) elog(ERROR, "out of shared memory"); if (!found) { initialize contents of shmem area; acquire any requested LWLocks using: ptr->mylockid = LWLockAssign(); } LWLockRelease(AddinShmemInitLock); }