33.10. ユーザ定義の型

項33.2に述べられているように、PostgreSQLは、新しい型をサポートするように拡張することができます。 本節では、SQL言語レベル以下で定義されるデータ型である基本型を新しく定義する方法について説明します。 新しい基本型の作成には、低レベル言語、通常Cで作成された型を操作する関数の実装が必要です。

実際に例を実行させられるように、本節で使用する例は、ソース配布物内のsrc/tutorialディレクトリにcomplex.sqlcomplex.cという名前で置いてあります。

ユーザ定義データ型では、必ず入力関数と出力関数が必要です。 これらの関数は、型が(ユーザによる入力とユーザへの出力のための)文字列中にどのような形式で表示されるかと、その型がメモリ中でどう構成されるかを決定します。 入力関数は引数としてヌル終端文字列を取り、その型の(メモリ中の)内部表現を返します。 出力関数は引数としてその型の内部表現を取り、ヌル終端文字列を返します。 単に格納するだけではなく、その型に何かそれ以上のことを行ないたいのであれば、その型用に持たせたい操作を実装した関数を更に提供しなければなりません。

例えば、複素数を表現するcomplex型を定義することを考えます。 おそらく、次のようなC構造体で複素数をメモリ中で表現することがごく自然な方法です。

typedef struct Complex {
    double      x;
    double      y;
} Complex;

単一のDatum値で扱うには大き過ぎるので、これは参照渡し型にしなければなりません。

この型の外部文字列表現として(x,y)形式の文字列を使用することを選択するでしょう。

入出力関数、特に出力関数を記述するのは困難ではありません。 しかしながら、この型の外部表現文字列を定義する時、その表現のための完全で堅牢なパーサを入力関数として書かなければなりません。 以下に例を示します。

PG_FUNCTION_INFO_V1(complex_in);

Datum
complex_in(PG_FUNCTION_ARGS)
{
    char       *str = PG_GETARG_CSTRING(0);
    double      x,
                y;
    Complex    *result;

    if (sscanf(str, " ( %lf , %lf )", &x, &y) != 2)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for complex: \"%s\"",
                        str)));

    result = (Complex *) palloc(sizeof(Complex));
    result->x = x;
    result->y = y;
    PG_RETURN_POINTER(result);
}

出力関数は以下のように簡単にできます。

PG_FUNCTION_INFO_V1(complex_out);

Datum
complex_out(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    char       *result;

    result = (char *) palloc(100);
    snprintf(result, 100, "(%g,%g)", complex->x, complex->y);
    PG_RETURN_CSTRING(result);
}

入出力関数は、各々の逆関数になるようにするべきです。 そうしないと、データをファイルにダンプし、それを読み戻そうとする際に、深刻な問題が発生するでしょう。 これは、浮動小数が関係する際によく発生する問題です。

オプションとして、ユーザ定義型は、バイナリ入出力関数を提供することができます。 バイナリ入出力は通常高速ですが、テキスト入出力より移植性がありません。 テキスト入出力と同様に、外部バイナリ表現を正確に定義することは作成者の責任です。 ほとんどの組み込みデータ型は、マシンに依存しないバイナリ表現を提供しようとしています。 complexでは、float8型のバイナリ入出力変換器を元にします。

PG_FUNCTION_INFO_V1(complex_recv);

Datum
complex_recv(PG_FUNCTION_ARGS)
{
    StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
    Complex    *result;

    result = (Complex *) palloc(sizeof(Complex));
    result->x = pq_getmsgfloat8(buf);
    result->y = pq_getmsgfloat8(buf);
    PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(complex_send);

Datum
complex_send(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    StringInfoData buf;

    pq_begintypsend(&buf);
    pq_sendfloat8(&buf, complex->x);
    pq_sendfloat8(&buf, complex->y);
    PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

complex型を定義する時、その型を生成する前にユーザ定義の入出力関数を生成する必要があります。

CREATE FUNCTION complex_in(cstring)
    RETURNS complex
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_out(complex)
    RETURNS cstring
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_recv(internal)
   RETURNS complex
   AS 'filename'
   LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_send(complex)
   RETURNS bytea
   AS 'filename'
   LANGUAGE C IMMUTABLE STRICT;

入出力関数の宣言では、まだ未定義の型を参照しなければならないことに注目してください。 これは許される操作です。警告メッセージが現れますが、無視して構いません。 入力関数を最初に記述しなければなりません。

最後に、データ型を宣言することができます。

CREATE TYPE complex (
   internallength = 16, 
   input = complex_in,
   output = complex_out,
   receive = complex_recv,
   send = complex_send,
   alignment = double
);

新しい基本型を定義すると、PostgreSQLは自動的にその型の配列のサポートを提供します。 歴史的な理由により、配列型は基本型の名前の前にアンダースコア文字_が付いた名前になります。

データ型が存在するようになると、更に、そのデータ型に対して有用な操作を提供する関数を宣言することができます。 そして、その関数を使用する演算子も定義できます。 また、必要に応じて、そのデータ型用のインデックスをサポートするために演算子クラスも作成可能です。 こうした追加層については後の節で説明します。

ユーザ定義データ型の値が(内部書式で)数百バイトを越えるのであれば、そのデータ型をTOAST化可能にしなければなりません。 このためには、内部表現が可変長データの標準レイアウトに従っていなければなりません。 先頭の4バイトはint32型で、(それ自身を含めた)データの合計バイト数を格納します。 そのデータ型を扱うC関数は、渡されたTOAST化値を注意深く展開しなければなりません。 (通常、詳細はGETARGマクロ内に隠蔽されています。) その後、CREATE TYPEコマンドを実行する際に、variableに内部長を指定し、また、適当な保存オプションを選択して下さい。

詳細についてはCREATE TYPEコマンドの説明を参照してください。