33.11. ユーザ定義の型

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

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

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

例えば、複素数を表現する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));
}

入出力関数を作成し共有ライブラリ内にコンパイルすれば、SQLのcomplex型を定義することができます。 まずシェル型として宣言します。

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

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