33.13. インデックス拡張機能へのインタフェース

これまでのところでは、新しい型や新しい関数、および新しい演算子をどの様に定義するかについて説明してきました。 しかしながら、新しい型の列に対するインデックスをまだ作成することができません。 このためには、新しいデータ型に対する演算子クラスを定義する必要があります。 本節で、複素数を値の絶対値の昇順にソートし格納するB-treeインデックスメソッドを使った新しい演算子クラスについての実行例によって、演算子クラスの概念を説明します。

注意: PostgreSQLリリース7.3より前のバージョンでは、ユーザ定義の演算子クラスを作成するために、pg_amoppg_amproc、および pg_opclassに手動での追加を行う必要がありました。 この方法は現在では推奨されておらず、CREATE OPERATOR CLASSが使用されています。 CREATE OPERATOR CLASSを使用すると、必要なカタログエントリを、より簡単に、よりエラーが発生しにくい方法で作成することができます。

33.13.1. インデックスメソッドとオペレータクラス

pg_amテーブルには各インデックスメソッド(内部ではアクセスメソッドとして知られています。)に対して1行ずつ含まれています。 普通のテーブルへの通常のアクセスのサポートはPostgreSQLに組み込まれていますが、すべてのインデックスメソッドは、pg_amで記述されています。 必要なインタフェースルーチンを定義し、pg_amに行を作成することによって、新しいインデックスメソッドを追加することが可能です。 しかし、この方法については、この章では記述していません。

インデックスメソッドのルーチンには、直接的にインデックスメソッドが演算するデータ型の情報は何も与えられていません。 代わりに、演算子クラスが、特定のデータ型の操作においてインデックスメソッドが使用する必要のある演算の集合を識別します。 演算子クラスという名前の由来は、それらが指定するものの1つにインデックスで使用できる(つまり、インデックススキャン条件に変換できる)WHERE句演算子の集合があるからです。 また、演算子クラスは、インデックスメソッドの内部演算で必要な、しかしインデックスで使用できるWHERE句演算子には直接的には対応しない、サポートプロシージャをいくつか指定することができます。

同じ入力データ型およびインデックスメソッドに対して複数の演算子クラスを定義することが可能です。 これにより、1つのデータ型に対して、複数のインデックスセマンティックの集合を定義することができます。 たとえば、B-treeインデックスでは、処理するデータ型ごとにソート順を定義する必要があります。 複素数データ型では、複素数の絶対値によりデータをソートするB-tree演算子クラスと、実部の数値によりソートするB-tree演算子クラスを持つといった方法は、有用かもしれません。 通常は演算子クラスの1つが一般的に最も有用であると判断され、そのデータ型およびインデックメソッドに対するデフォルトの演算子クラスとして設定されます。

複数の異なるインデックスメソッドに、同一の演算子クラス名を使用することができます (例えば、B-treeとハッシュインデックスメソッドは、両方ともoid_opsという名前の演算子クラスを持つことができます)。 ただし、そのような各クラスは独立した実体であり、別々に定義される必要があります。

33.13.2. インデックスメソッドのストラテジ

演算子クラスに関連付けられている演算子は、"ストラテジ番号"により識別されます。 "ストラテジ番号"は、演算子クラスのコンテキスト内における各演算子のセマンティクスを識別するためのものです。 たとえば、B-treeの場合、キーが小さい方から大きい方へ厳密に並んでいなければなりません。 したがって、B-treeに関しては、"より小さい"および"以上"のような演算子は興味深いと言えます。 PostgreSQLではユーザが演算子を定義できるため、PostgreSQLは演算子の名前(たとえば<>=)を見つけても、その演算子がどのような比較を行なうかを判断することはできません。 その代わり、インデックスメソッドは"ストラテジ"の集合を定義します。 "ストラテジ"は、汎用演算子と考えることができます。 各演算子クラスは、特定のデータ型およびインデックスセマンティックの解釈において、実際のどの演算子が各ストラテジに対応しているかを指定します。

表33-2に示すように、B-treeインデックスメソッドではストラテジを5つ定義します。

表 33-2. B-tree ストラテジ

演算ストラテジ番号
小なり1
以下2
等しい3
以上4
大なり5

ハッシュインデックスは、ビット毎の等価性のみを表します。 したがって、表33-3に示すように、ストラテジを1つのみ定義します。

表 33-3. ハッシュストラテジ

演算ストラテジ番号
等しい1

R-treeインデックスは矩形包含関係を表します。 表33-4に示すように、R-treeインデックスはストラテジを8つ定義します。

表 33-4. R-treeストラテジ

演算ストラテジ番号
より左1
より左または重なる2
重なる3
より左または重なる4
より右5
同じ6
含む7
含まれる8

GiSTインデックスは、より柔軟です。 固定のストラテジの集合を全く持ちません。 代わりに、特定のGiST演算子クラスの"一貫性"サポートルーチンが、ストラテジ番号が何を意味するかを解釈します。

すべてのストラテジ演算子はブール値を返すことに注意してください。 実際、インデックスで使用されるためにWHEREの最上位レベルで現れなければなりませんので、インデックスメソッドストラテジとして定義されたすべての演算子の戻り値の型はbooleanでなければなりません。

ところで、pg_amamorderstrategy列は、インデックスメソッドが順スキャンをサポートするかどうかを示します。 ゼロはサポートしないことを意味します。 サポートする場合は、amorderstrategyが順序付け用演算子に対応するストラテジ番号になります。 例えば、B-treeではamorderstrategy = 1となっており、これは"小なり"のストラテジ番号です。

33.13.3. インデックスメソッドのサポートルーチン

ストラテジは通常、システムがインデックスを使う方法を判断するために十分な情報ではありません。 実際には、インデックスメソッドは、作業のために追加のサポートルーチンを必要とします。 たとえばB-treeインデックスメソッドは、2つのキーを比較し、より大きいのか、等しいのか、より小さいのかを決定できなければなりません。 同様に、R-treeアクセスメソッドは 四角形の共通部分、和集合、サイズなどを計算できなければなりません。 これらの操作はSQLコマンドの条件内で使用される演算子とは対応しません。 これらはインデックスメソッドで内部的に使用される管理用ルーチンです。

ストラテジと同じように、演算子クラスにより、与えられたデータ型およびセマンティック解釈に対して、どの特定の関数がこれらの各役割を果たすべきであるかが識別されます。 インデックスメソッドは必要な関数の集合を定義し、演算子クラスは、これらを"サポート関数番号"に代入することによって、使用すべき正しい関数を識別します。

表33-5に示すように、B-treeには単一のサポート関数が必要です。

表 33-5. B-tree サポート関数

関数サポート番号
2つのキーを比較し、最初のキーが2番目のキーより小さいか、等しいか、大きいかを示す、0未満、0、もしくは0より大きい整数を返します。 1

表33-6に示すように、ハッシュインデックスも同様に単一のサポート関数が必要です。

表 33-6. ハッシュサポート関数

関数サポート番号
キーのハッシュ値を計算1

表33-7に示すように、R-treeインデックスには3つのサポート関数が必要です。

表 33-7. R-treeサポート関数

関数サポート番号
和集合1
共通部分2
サイズ3

表33-8に示すように、GiSTインデックスには7つのサポート関数が必要です。

表 33-8. GiSTサポート関数

関数サポート番号
一貫性1
和集合2
圧縮3
伸長4
ペナルティ5
分割選択6
等しい7

ストラテジ演算と異なり、サポート関数は特定のインデックスメソッドが想定するデータ型、例えば、B-tree用の比較関数の場合、符号付き整数、を返します。

33.13.4. 例

ここまでで概念について説明してきました。 ここで、新しい演算子クラスを作成する有用な例を紹介します。 (この例を作業できるように、ソース配布物内のsrc/tutorial/complex.csrc/tutorial/complex.sqlにコピーがあります。) この演算子クラスは、複素数をその絶対値による順番でソートする演算子をカプセル化します。 ですので、その名前にcomplex_abs_opsを選びました。 最初に、演算子の集合が必要になります。 演算子を定義する処理は項33.11で説明しました。 B-tree上の演算子クラスでは、以下の演算子が必要です。

比較演算子の関連する集合を定義する時にエラーを発生を最小にする方法は、まず、B-tree比較サポート関数を作成し、その後に、他の関数をサポート関数に対する1行のラッパとして作成することです。 これにより、重箱の隅で一貫性のない結果を得る確率が減少します。 この手法に従って、まず以下を作成します。

#define Mag(c)  ((c)->x*(c)->x + (c)->y*(c)->y)

static int
complex_abs_cmp_internal(Complex *a, Complex *b)
{
    double      amag = Mag(a),
                bmag = Mag(b);

    if (amag < bmag)
        return -1;
    if (amag > bmag)
        return 1;
    return 0;
}

これで、小なり関数は以下のようになります。

PG_FUNCTION_INFO_V1(complex_abs_lt);

Datum
complex_abs_lt(PG_FUNCTION_ARGS)
{
    Complex    *a = (Complex *) PG_GETARG_POINTER(0);
    Complex    *b = (Complex *) PG_GETARG_POINTER(1);

    PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0);
}

他の4関数での違いは、内部関数の結果とゼロとをどのように比べるかだけです。

次に、関数と、この関数に基づく演算子をSQLで宣言します。

CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
    AS 'filename', 'complex_abs_lt'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR < (
   leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
   commutator = > , negator = >= ,
   restrict = scalarltsel, join = scalarltjoinsel
);

正しく交換演算子と否定子演算子を指定する他、適切な制限選択性関数と結合関数を指定することが重要です。 さもないと、オプティマイザはインデックスを効率的に使用することができません。 小なり、等価、大なりの場合に異なる選択性関数を使用しなければならないことに注意してください。

他にも、価値がないことがここで発生します。

次のステップは、B-treeに必要なサポートルーチンの登録です。 これを実装するCコードは、演算子関数と同じファイルに入っています。 以下は、関数をどのように宣言するかを示します。

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

これまでで、必要な演算子およびサポートルーチンを持つようになりました。 最後に演算子クラスを作成することができます。

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       complex_abs_cmp(complex, complex);

これで終りです! これでcomplex列にB-treeインデックスを作って使用することが可能になったはずです。

以下のように、演算子エントリをより冗長に記述することができます。

        OPERATOR        1       < (complex, complex) ,

しかし、演算子が、演算子クラスの定義と同一のデータ型である場合、このような記述をする必要はありません。

上記の例は、ユーザがこの新しい演算子クラスをcomplexデータ型のデフォルトのB-tree演算子クラスにしようとしていると仮定しています。 このようにしない場合、DEFAULTという単語を取り除いてください。

33.13.5. システムの演算子クラスに対する依存性

PostgreSQLは演算子クラスを、単にインデックスで使用できるかどうかだけではなく、多くの方式で演算子の性質を推定するために使用します。 従って、データ型の列をインデックス付けするつもりがなくても、演算子クラスを作成した方がよい可能性があります。

具体的には、ORDER BYDISTINCTなど、値の比較とソートを必要とするSQL機能があります。 ユーザ定義のデータ型に対してこの機能を実装するために、PostgreSQLはそのデータ型用のデフォルトのB-tree演算子クラスを検索します。 この演算子クラスの"等価判定"メンバが、GROUP BYDISTINCT用の値の等価性についてのシステムの意向を定義し、この演算子クラスによって強制されるソート順序が、デフォルトのORDER BY順序を定義します。

また、ユーザ定義型の配列の比較は、デフォルトのB-tree演算子クラスによって定義されるセマンティックに依存します。

データ型用のデフォルトのB-tree演算子クラスが存在しないと、システムはデフォルトのハッシュ演算子クラスを検索します。 しかし、この種類の演算子クラスは等価性のみを提供しますので、実際にこれは、配列等価性のサポートだけに対して十分です。

データ型用のデフォルトの演算子クラスが存在しない場合に、こうしたSQL機能をデータ型に使用しようとすると、"演算子クラスを識別できなかった"といったエラーとなります。

注意: PostgreSQLバージョン7.4より前まででは、 ソートやグループ化操作は暗黙的に=<>という名前の演算子を使用していました。 この新しい、デフォルトの演算子クラスに依存する振舞いによって、特定の名前を持つ演算子の振舞いについて何らかの仮定を立てることを防止しています。

33.13.6. 演算子クラスの特殊な機能

演算子クラスには、まだ説明していない2つの特殊な機能があります。 説明していない主な理由は、最も良く使用するインデックスメソッドでは、これらがあまり有用ではないためです。

通常、演算子を演算子クラスのメンバとして宣言すると、インデックスメソッドでその演算子を使用して、WHERE条件を満たす行の集合を正確に抽出することができます。 以下に例を示します。

SELECT * FROM table WHERE integer_column < 4;

この式は、整数列にB-treeインデックスを使用することにより、正確に満たすことができます。 しかし、マッチする行へのあいまいなガイドとして、インデックスが有用である場合があります。 たとえば、R-treeインデックスで、オブジェクトの境界ボックスのみを格納したとします。 その結果、多角形のような長方形でないオブジェクトとの重なりをテストするWHERE条件は正確に満たすことができません。 もっとも、このインデックスを使用して、対象オブジェクトの境界ボックスに重なる境界ボックスを持つオブジェクトを検索し、さらに、検索されたオブジェクトのみに対して正確に重なるかどうかをテストすることはできます。 このような場合、インデックスは、演算子に対して"無駄が多い"ということになります。 そこで、CREATE OPERATOR CLASSコマンドのOPERATOR句に、RECHECKを追加します。 RECHECKは、インデックスが、すべての必要な行、およびおそらくいくつかの追加の行を返すことを保証されている場合に、有効です。 この追加の行は、元の演算子呼び出しを実行することによって除外できます。

再度、多角形のような複雑なオブジェクトの境界ボックスのみをインデックスに格納している状況を考えてみてください。 この場合、インデックスエントリに多角形全体を格納するのは、それ程有用なことではありません。 単に、より単純なbox 型のオブジェクトを格納した方が良いかもしれません。 このような状況は、CREATE OPERATOR CLASSSTORAGEオプションによって表現することができます。 たとえば、以下のように記述します。

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

現時点では、GiSTアクセスメソッドのみが、列のデータ型と異なるSTORAGE型をサポートしています。 STORAGEが使用された場合、GiSTのcompressおよびdecompressサポートルーチンは、データ型を変換する必要があります。