33.7. C言語関数

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

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

33.7.1. 動的ロード

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

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

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

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

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

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

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

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

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

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

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

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

PostgreSQLリリース7.2以前では、CREATE FUNCTIONで指定できるオブジェクトファイルは、正確な絶対パスだけでした。 関数定義を不必要に移植できなくしていますので、この方法は古い方法とされています。 パスや拡張子を付けずに共有ライブラリを単に指定し、検索機構によってその情報を提供させることが最善です。

33.7.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の型の実装サンプルを示します。

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

それらの型のポインタのみがPostgreSQL関数の入出力時に使用できます。 それらの型の値を返すためには、palloc()を使用して正しい大きさのメモリ領域を割り当て、そのメモリ領域に値を入力し、それのポインタを返します。 (あるいは、入力値を指し示すポインタを返すことによって、戻り値と同じ型の入力値を返すことも可能です。 しかし、参照渡しの入力値の内容は決して変更しないでください)。

最後に、すべての可変長型は参照として引き渡す必要があります。 また、すべての可変長型は正確に4バイトのlengthフィールドから始まる必要があり、その型に格納されるすべてのデータはlengthフィールドのすぐ後のメモリ領域に置かれる必要があります。 lengthフィールドにはその構造体の総長が格納されます。つまり、lengthフィールドそのものもその大きさに含まれます。

例えば、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);
...

VARHDRSZsizeof(int4)と同一ですが、可変長型のオーバーヘッド分の大きさを参照する時には、VARHDRSZマクロを使用する方が好ましい形式とみなされています。

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

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

SQL型 C 言語型 定義場所
abstimeAbsoluteTimeutils/nabstime.h
booleanboolpostgres.h (コンパイラで組み込み済みの可能性があります)
boxBOX*utils/geo_decls.h
byteabytea*postgres.h
"char"char(compiler built-in)
characterBpChar*postgres.h
cidCommandIdpostgres.h
dateDateADTutils/date.h
smallint (int2)int2 or int16postgres.h
int2vectorint2vector*postgres.h
integer (int4)int4 or int32postgres.h
real (float4)float4*postgres.h
double precision (float8)float8*postgres.h
intervalInterval*utils/timestamp.h
lsegLSEG*utils/geo_decls.h
nameNamepostgres.h
oidOidpostgres.h
oidvectoroidvector*postgres.h
pathPATH*utils/geo_decls.h
pointPOINT*utils/geo_decls.h
regprocregprocpostgres.h
reltimeRelativeTimeutils/nabstime.h
texttext*postgres.h
tidItemPointerstorage/itemptr.h
timeTimeADTutils/date.h
time with time zoneTimeTzADTutils/date.h
timestampTimestamp*utils/timestamp.h
tintervalTimeIntervalutils/nabstime.h
varcharVarChar*postgres.h
xidTransactionIdpostgres.h

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

33.7.3. C言語関数のためのVersion-0呼び出し規約

まず最初に、現在は非推奨ですが理解しやすいので、"古いスタイル"の呼び出し規約を記述します。 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(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規約ではこれらの問題が解決されています。

33.7.4. C言語関数のための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));
    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マクロは"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を使用して指定できます。 ストレージの型は、plainexternalextended、または mainのいずれかです。)

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

33.7.5. コードの作成

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

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

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

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

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

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

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

BSD/OS

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

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

これはBSD/OSのバージョン4.0に適用されます。

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

MacOS 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で省略することができ、そして移植性を最も高くするため通常は省略されます。

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

33.7.7. C言語関数における複合型引数

複合型では、Cの構造体のような固定のレイアウトがありません。 複合型のインスタンスはヌルフィールドを持つ可能性があります。 更に、複合型で、継承階層の一部であるものは同じ継承階層の他のメンバとは異なるフィールドを持つ可能性もあります。 そのため、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(TupleTableSlot *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)
{
    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);
    /* この他、salaryがNULLの場合用にPG_RETURN_NULL()を行なった方がいいでしょう */

    PG_RETURN_BOOL(salary > limit);
}

GetAttributeByNameは、指定された行から属性を返す、PostgreSQLシステム関数です。 これには3つの引数があります。 それらは、関数に渡されたTupleTableSlot*型の引数、求められた属性の名前、属性がNULLであるかどうかを通知する返りパラメータです。 GetAttributeByNameは適切な DatumGetXXX()マクロを使用して適切なデータ型に変換可能なDatum型の値を返します。

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

CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
    AS 'DIRECTORY/funcs', 'c_overpaid'
    LANGUAGE C;

33.7.8. C言語関数から行(複合型)を返す

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

#include "funcapi.h"

複合データ型(行)を返す機能はAttInMetadataで始まります。 この構造体は、未加工のC文字列から行を作成するために必要とされる個々の属性情報を配列として保持します。 この情報はTupleDesc構造体より派生するものですが、集合を返す関数(次節参照)を呼び出す際の冗長な計算を防ぐためにここでも保存されます。 集合を返す関数の場合では、AttInMetadata構造体は初回の呼び出しのみ計算され、以降の呼び出しで再利用するために保存されます。 また、AttInMetadataは元となるTupleDescへのポインタも保存します。

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を直接タプルに変換する方法を使用してください。 その場合でもTupleDescTupleTableSlotは必要ですが、AttInMetadataは必要ありません。

関数から返すタプルを作成したら、そのタプルをDatumに変換する必要があります。 それには、

TupleGetDatum(TupleTableSlot *slot, HeapTuple tuple)

を使用してタプルとスロットで与えられるDatumを取得します。 このDatumは、1行のみを返す場合には直接返すことができます。 また、集合を返す関数の現在の結果値として使用することもできます。

次節に例を示します。

33.7.9. C言語関数から集合を返す

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;

    /*
     * 省略可能 : 結果スロットへのポインタ
     * 
     * スロットはタプル(つまり複合データ型)を返す際に使用され、
     * 基本データ型を返す場合には必要ありません。
     */
    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)

を使用して、そのデータを呼び出し側に返します。 (先に説明したとおり resultDatum型、つまり、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);
        /* 1 度限りのセットアップコードがここに入ります: */
        user code
        if returning composite
            build TupleDesc, and perhaps AttInMetadata
            obtain slot
            funcctx->slot = slot;
        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(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);

        /*  __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 配列を準備。
         * これは、後で適切な入力関数で処理される
         * 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 integer, f2 integer, f3 integer);

CREATE OR REPLACE FUNCTION testpassbyval(integer, integer) RETURNS SETOF __testpassbyval
    AS 'filename', 'testpassbyval'
    LANGUAGE C IMMUTABLE STRICT;

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

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

C言語関数は、anyelementおよびanyarray多様型を受け付ける、または、返すように宣言することができます。 多様関数の詳細な説明は項33.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から始まります。

例えば、任意の型の単一要素を受け付け、その型の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;
    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");

    /* 与えられた要素を取り出す。 */
    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, 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 STRICT;

STRICTの使用に注目してください。 コード内でNULL入力のテストを行なっていませんので、これは重要です。