34.11. ユーザ定義の型

項34.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可能としなければなりません。 (項53.2を参照してください。) ヘッダのオーバーヘッドを減らすことで、TOASTは小さなデータに対しても容量を抑えることができますので、データが常に圧縮し外部に格納するには小さ過ぎる場合でも、これを行わなければなりません。

このためには、内部表現が可変長データの標準レイアウトに従っていなければなりません。 先頭の4バイトはchar[4]フィールドで、直接アクセスされることは決してありません(慣習的にvl_len_と呼ばれます)。 SET_VARSIZE()を使用してdatumのサイズをこのフィールドに格納し、VARSIZE()を使用して取り出さなければなりません。 そのデータ型を扱うC関数は、PG_DETOAST_DATUMを使用して、渡されたTOAST化値を注意深く展開しなければなりません (通常、詳細は型独自のGETARG_DATATYPE_Pマクロを定義して隠蔽します)。 その後、CREATE TYPEコマンドを実行する際に、variableに内部長を指定し、また、適当な保存オプションを選択してください。

整列が(単なる特定の関数向けやデータ型が常にバイト単位の整列を規定しているため)重要でない場合、PG_DETOAST_DATUMのオーバヘッドの一部を省くことができます。 代わりにPG_DETOAST_DATUM_PACKEDを使用してください(通常はPG_DETOAST_DATUM_PACKEDマクロを定義することで隠蔽されます)。 そして、VARSIZE_ANY_EXHDRおよびVARDATA_ANYマクロを使用して、圧縮されている可能性があるdatamにアクセスしてください。 繰り返しますが、これらのマクロから返されるデータは、たとえデータ型定義で整列を規定していたとしても、整列されません。 整列が重要であれば、通常のPG_DETOAST_DATUMインタフェースを介して実行してください。

注意: 古めのコードではしばしばvl_len_char[4]ではなくint32として宣言しています。 この構造体定義が少なくともint32で整列されたフィールドを持っている限り、これは問題ありません。 しかし、整列されていない可能性があるdatumを取り扱う場合に、こうした構造体定義を使用することは危険です。 datumが実際に整列されていると仮定することをコンパイラの規則としているかもしれず、この場合、整列に厳密なアーキテクチャではコアダンプしてしまいます。

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

アダルトレンタルサーバー