Postgres は、その内部で、基本型を "メモ リ内の小塊" とみなしています。ある型について定義したユーザ定義関数 は同様に Postgres がその型をどう操作で きるのかを定義しています。つまり、 Postgres はデータをディスクに保存し、ディスクからデータを受取るだけで、その データを入力として受取り、処理し、出力するためにユーザ定義の関数を 使います。基本型は次の 3 つの内部フォーマットのいずれかを持ちます。
値渡し、固定長
参照渡し、固定長
参照渡し、不定長
値渡しの型は 1、 2、又は 4 バイト長のみです。(使用するコンピ ュータが他の大きさの値渡しの型をサポートしていたとしてもです。) Postgres 自身では、整数型のみを値で渡 します。型を定義する時、その型が全てのアーキテクチャで同一の大き さ(バイト数)になることに注意して下さい。例えば、 long 型は、あるマシンでは 4 バイト、他では 8 バ イトであるので危険です。一方 int は(ほとんどの パーソナルコンピュータでは異なりますが)ほとんどの UNIX マシンで 4 バイトになっています。 UNIX マシンでの int4 型の合理 的な実装は次になるでしょう。
/* 4 バイト整数、値渡し */ typedef int int4;
一方、固定長の任意の大きさの型は参照渡しで渡されます。例えば、以 下は Postgres の型の実装のサンプルです。
/* 16 バイトの構造体、参照渡し */ typedef struct { double x, y; } Point;
Postgres 関数の入出力にこのような型 が渡された場合はポインタのみが使用される事ができます。最後に、全 ての不定長の型は参照で渡される必要があります。全ての不定長の型は正 確に 4 バイトの length フィールドから始まる必要があり、その型に保 存される全てのデータは length フィールドのすぐ後に続いて、メモリに 置かれる必要があります。length フィールドはその構造体の総長です。 (つまり、length フィールド自体もその大きさに含みます。)次のよう に text 型を定義できます。
typedef struct { int4 length; char data[1]; } text;
明らかに、 data フィールドはすべての取り得る文字列を保持できるほど の長さをもっていません。C ではそのような構造体を 宣言できません。不定長の型を操作する時、正しい大きさのメモリを割り当 て、 length フィールドを初期化することに注意する必要があります。例え ば、text フィールドに 40 バイトを保持したい場合、下のようなコードを 使用する事になります。
#include "postgres.h" ... char buffer[40]; /* our source data */ ... text *destination = (text *) palloc(VARHDRSZ + 40); destination->length = VARHDRSZ + 40; memmove(destination->data, buffer, 40); ...
これで基本型用の全てのあり得る構造体について完了しましたので、実 際の関数の例を幾つか示す事ができます。下のような funcs.c を前提にします。
#include <string.h> #include "postgres.h" /* 値渡し */ int add_one(int arg) { return(arg + 1); } /* 参照渡し、固定長 */ 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)); memset(new_t, 0, VARSIZE(t)); VARSIZE(new_t) = VARSIZE(t); /* * VARDATA は構造体の data 領域へのポインタ */ 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); memset((void *) new_text, 0, new_text_size); VARSIZE(new_text) = new_text_size; strncpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ); strncat(VARDATA(new_text), VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ); return (new_text); }
OSF/1 では、次のように入力します。
CREATE FUNCTION add_one(int4) RETURNS int4 AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION makepoint(point, point) RETURNS point AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION concat_text(text, text) RETURNS text AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION copytext(text) RETURNS text AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c';
他のシステムでは、ファイル名の終りを(共有ライブラリを示す).sl にしなければいけないかもしれません。
複合型は C の構造体のような固定のレイアウトをもちません。複 合型のインスタンスは NULL フィールドを持つかもしれません。更に、 継承階層の一部である複合型は、同じ継承の階層の他のメンバとは異 なるフィールドを持つ可能性もあります。そのため、 Postgres は C から複合型のフィールド にアクセスするための手続きのインタフェースを提供します。 Postgres はインスタンスの集合を処理す るため、各インスタンスは、不明瞭な 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" /* GetAttributeByName() 関数のための宣言 */ bool c_overpaid(TupleTableSlot *t, /* 対象 EMP 型のインスタンス */ int4 limit) { bool isnull = false; int4 salary; salary = (int4) GetAttributeByName(t, "salary", &isnull); if (isnull) return (false); return(salary > limit); }
GetAttributeByName は、対象インスタンスの属 性を返す Postgres システム関数です。 その関数に渡される TUPLE 型の引数、要求する属性の名前、属性が NULL かどうかを示すリターンパラメータという 3 つの引数を取ります。 GetAttributeByName は、その戻り値を必要とす る型にキャストできるようにデータを適切に整列します。例えば、name 型の属性の名前を指定する時、GetAttributeByName は下のようになります。
char *str; ... str = (char *) GetAttributeByName(t, "name", &isnull)
次の問い合わせは Postgres に c_overpaid 関 数を伝えます。
* CREATE FUNCTION c_overpaid(EMP, int4) RETURNS bool AS 'PGROOT/tutorial/obj/funcs.so' LANGUAGE 'c';
C 関数内から新しいインスタンスを構築したり既存のインスタンスを 変更する方法はありますが、このマニュアルで説明するには複雑過ぎ ます。
さてここからプログラミング言語関数を記述する上でより難しい仕事 について説明します。このマニュアルはプログラマを養成するため のものではありません。Postgres で使 用する C 関数を作成しようとする前には、(ポ インタや malloc メモリ管理を含め、)C につい てよく理解していなければいけません。C 以外の 言語で記述した関数を Postgres に組み 込む事はできますが、FORTRAN や Pascal といった他の言語は多くの場合 C 同様の "呼び出し規定" に従っていませんので (できる事はできるのですが)多くの場合は困難です。つまり他の言語 では同じ方法で引数を渡したり結果を返すことを行ないません。この理 由のためにプログラミング言語関数は C で記述さ れているものと仮定します。C 関数を作成する時の 基本的なルールは次のものです。
ほとんどの Postgres 用の (include)ヘッダファイルは PGROOT/include にインストールされています。(図 2 を見て下さい。)cc のコマンドラインに下を常に含めるべきです。
-I$PGROOT/include必要とするヘッダファイルの一部分は、サーバーのソース自 体の中にあるものがあります。(つまりシステムインストー ル時に include 内にインストールしなかったファイルを必要 とします。)この場合、次のものの一つ以上を指定する必要が あります。
-I$PGROOT/src/backend -I$PGROOT/src/backend/include -I$PGROOT/src/backend/port/<PORTNAME> -I$PGROOT/src/backend/obj(ここで <PORTNAME> は alpha 、 sparc といった移植先 の名前です。)
メモリを割り当てる時には、 palloc と pfree という Postgres のルーチンを対応する malloc と free という C ライブラリの ルーチンの代わりに使用して下さい。palloc で割り当てられた メモリは、メモリリークを防ぐために、各トランザクションが 終了した時点で自動的に解放されます。
memset 又は bzero を使って、常に構造体全体をゼロにして下さ い。(ハッシュアクセスメソッド、ハッシュ結合、ソートアルゴ リズムなどの)多くのルーチンは構造体内のビットそのものを使って その計算を行ないます。構造体の全メンバを初期化したとして も、整列させるための(構造体中のすき間を)埋める物の多くの バイトはゴミの値を持ちます。
Postgres の内部的な型のほとん どは postgres.h で宣言されていますので、常にこのファイル を include しておく事は良い考えです。postgres.h を include することは、また、elog.h と palloc.h も include しているこ とになります。
動的に Postgres に組み込まれ るようにオブジェクトコードをコンパイルし読み込むには、常 に特別なフラグが必要です。特定のオペレーティングシステム でどのように指定するのかについての詳細は付録 A を見て下さ い。