これまでのところでは、新しい型や新しい関数、および新しい演算子をどの様に定義するかについて説明してきました。 しかしながら、新しい型の列に対するインデックスをまだ作成することができません。 このためには、新しいデータ型に対する演算子クラスを定義する必要があります。 本節では、複素数を値の絶対値の昇順にソートし格納するB-treeインデックスメソッドを使った新しい演算子クラスについての実行例を用いて、演算子クラスの概念を説明します。
演算子クラスを演算子族にまとめ、意味的に互換性を持つクラス間の関係を表すことができます。 1つのデータ型のみが含まれる場合、演算子クラスで十分です。 そこでまずこうした状況に注目し、その後で演算子族に戻ります。
pg_am
テーブルには各インデックスメソッド(内部ではアクセスメソッドとして知られています)に対して1つの行が含まれています。
テーブルへの通常のアクセスのサポートはPostgreSQLに組み込まれていますが、すべてのインデックスメソッドは、pg_am
で記述されています。
必要なコードを書いた後、pg_am
にエントリを作成することによって、新しいインデックスアクセスメソッドを追加することができます。
しかし、この方法についての説明は本章での範囲を超えています(第64章を参照してください)。
インデックスメソッドのルーチンには、直接的にインデックスメソッドが演算するデータ型の情報は何も与えられていません。
代わりに、演算子クラスが、特定のデータ型の操作においてインデックスメソッドを使用する必要がある演算の集合を識別します。
演算子クラスという名前の由来は、それらが指定するものの1つにインデックスで使用できる(つまり、インデックススキャン条件に変換できる)WHERE
句演算子の集合があるからです。
また、演算子クラスは、インデックスメソッドの内部演算で必要な、しかしインデックスで使用できるWHERE
句演算子には直接的には対応しない、サポート関数をいくつか指定することができます。
同じ入力データ型およびインデックスメソッドに対して複数の演算子クラスを定義することが可能です。 これにより、1つのデータ型に対して、複数のインデックス付けセマンティクスの集合を定義することができます。 例えば、B-treeインデックスでは、処理するデータ型ごとにソート順を定義する必要があります。 複素数データ型では、複素数の絶対値によりデータをソートするB-tree演算子クラスと、実部の数値によりソートするB-tree演算子クラスを持つといった方法は、有用かもしれません。 通常は演算子クラスの1つが一般的に最も有用であると判断され、そのデータ型およびインデックスメソッドに対するデフォルトの演算子クラスとして設定されます。
複数の異なるインデックスメソッドに、同一の演算子クラス名を使用することができます(例えば、B-treeとハッシュインデックスメソッドは、両方ともint4_ops
という名前の演算子クラスを持つことができます)。
ただし、そのような各クラスは独立した実体であり、別々に定義される必要があります。
演算子クラスに関連付けられている演算子は、「ストラテジ番号」により識別されます。
「ストラテジ番号」は、演算子クラスのコンテキスト内における各演算子のセマンティクスを識別するためのものです。
例えば、B-treeの場合、キーが小さい方から大きい方へ厳密に並んでいなければなりません。
したがって、B-treeに関しては、「より小さい」および「以上」のような演算子は興味深いと言えます。
PostgreSQLではユーザが演算子を定義できるため、PostgreSQLは演算子の名前(例えば<
や>=
)を見つけても、その演算子がどのような比較を行うかを判断することはできません。
その代わり、インデックスメソッドは「ストラテジ」の集合を定義します。
「ストラテジ」は汎用演算子と考えることができます。
各演算子クラスは、特定のデータ型およびインデックスセマンティクスの解釈において、実際のどの演算子が各ストラテジに対応しているかを指定します。
表 38.3に示すように、B-treeインデックスメソッドではストラテジを5つ定義します。
表38.3 B-treeストラテジ
演算 | ストラテジ番号 |
---|---|
小なり | 1 |
以下 | 2 |
等しい | 3 |
以上 | 4 |
大なり | 5 |
ハッシュインデックスは等価性のみをサポートします。 したがって、表 38.4に示すように、ストラテジを1つのみ定義します。
表38.4 ハッシュストラテジ
演算 | ストラテジ番号 |
---|---|
等しい | 1 |
GiSTインデックスはより柔軟です。 固定のストラテジの集合をまったく持ちません。 代わりに、特定のGiST演算子クラスの「consistent」サポートルーチンが、ストラテジ番号が何を意味するかを解釈します。 例として、2次元幾何オブジェクトをインデックス付けし、「R-tree」ストラテジを提供する組み込みのGiSTインデックス演算子クラスのいくつかを表 38.5に示します。 この内4個は2次元に対する(重複、合同、包含、被包含)試験です。 残りの内4個はX方向のみに対する、残り4個はY方向のみに対する同一の試験を提供します。
表38.5 GiSTによる2次元の「R-tree」ストラテジ
演算 | ストラテジ番号 |
---|---|
完全に左側 | 1 |
右側にはみ出さない | 2 |
重なる | 3 |
左側にはみ出さない | 4 |
完全に右側 | 5 |
同じ | 6 |
含む | 7 |
含まれる | 8 |
上側にはみ出さない | 9 |
完全に下側 | 10 |
完全に上側 | 11 |
下側にはみ出さない | 12 |
SP-GiSTインデックスは柔軟性という点でGiSTと似ており、固定のストラテジ群を持ちません。 その代わりに、各演算子クラスのサポートルーチンが演算子クラスの定義に従ってストラテジ番号を解釈します。 例として、点に対する組み込みの演算子クラスで使用されるストラテジ番号を表 38.6に示します。
表38.6 SP-GiSTの点に関するストラテジ
演算 | ストラテジ番号 |
---|---|
厳密に左側 | 1 |
厳密に右側 | 5 |
同一 | 6 |
包含される | 8 |
厳密に下 | 10 |
厳密に上 | 11 |
GINインデックスは、いずれも固定のストラテジ群を持たないという点で、GiSTおよびSP-GiSTインデックスと似ています。 その代わりに、各演算子クラスのサポートルーチンが演算子クラスの定義に従ってストラテジ番号を解釈します。 例として、配列に対する組み込みの演算子クラスで使用されるストラテジ番号を表 38.7に示します。
表38.7 GIN 配列のストラテジ
演算 | ストラテジ番号 |
---|---|
重複 | 1 |
包含 | 2 |
包含される | 3 |
等しい | 4 |
BRINインデックスは、いずれも固定のストラテジ群を持たないという点で、GiST、SP-GiSTおよびGINインデックスと似ています。
その代わりに、各演算子クラスのサポートルーチンが演算子クラスの定義に従ってストラテジ番号を解釈します。
例として、組み込みのMinmax
演算子クラスで使用されるストラテジ番号を表 38.8に示します。
表38.8 BRIN Minmaxストラテジ
演算 | ストラテジ番号 |
---|---|
小なり | 1 |
以下 | 2 |
等しい | 3 |
以上 | 4 |
大なり | 5 |
上記の演算子はすべて論理値を返すことに注意してください。
実際、インデックスで使用されるためにWHERE
の最上位レベルで現れなければなりませんので、インデックスメソッド検索演算子として定義された、すべての演算子の戻り値の型はboolean
でなければなりません。
(一部のインデックスアクセスメソッドは、通常論理型の値を返さない順序付け演算子もサポートします。
この機能については38.16.7で説明します。)
ストラテジは通常、システムがインデックスを使う方法を判断するために十分な情報ではありません。 実際には、インデックスメソッドが動作するためには、さらにサポートルーチンを必要とします。 例えばB-treeインデックスメソッドは、2つのキーを比較し、より大きいのか、等しいのか、より小さいのかを決定できなければなりません。 同様に、ハッシュインデックスは、キー値のハッシュコードを計算できなければなりません。 これらの操作はSQLコマンドの条件内で使用される演算子とは対応しません。 これらはインデックスメソッドで内部的に使用される管理用ルーチンです。
ストラテジと同じように、演算子クラスにより、与えられたデータ型およびセマンティクス解釈に対して、どの特定の関数がこれらの各役割を果たすべきであるかが識別されます。 インデックスメソッドは必要な関数の集合を定義し、演算子クラスは、これらをインデックスメソッドで指定された「サポート関数番号」に代入することによって、使用すべき正しい関数を識別します。
さらに、演算子クラスの中には、ユーザがその振る舞いを制御するパラメータを指定できるものもあります。
各組み込みインデックスアクセスメソッドには省略可能なoptions
サポート関数があり、演算子クラスに固有のパラメータの集合を定義しています。
表 38.9に示すように、B-treeは比較サポート関数を必須とし、演算子クラスの作者が望めば4つの追加サポート関数を与えることができます。 これらのサポート関数の要件は67.3でさらに詳しく解説されています。
表38.9 B-treeサポート関数
関数 | サポート番号 |
---|---|
2つのキーを比較し、最初のキーが2番目のキーより小さいか、等しいか、大きいかを示す、0未満、0、もしくは0より大きい整数を返します | 1 |
C言語から呼び出し可能なソートサポート関数のアドレスを返します(省略可能) | 2 |
テスト値をベース値にオフセットを加減算したものと比較して、比較結果に従って真または偽を返します(省略可能) | 3 |
演算子クラスを使うインデックスがB-tree重複排除最適化を安全に適用できるかどうかを決定します(省略可能) | 4 |
この演算子クラスに固有のオプションを定義します(省略可能) | 5 |
表 38.10に示すようにハッシュインデックスでは一つのサポート関数が必須で、演算子クラス作者が望むなら、さらに2つのサポート関数を与えることができます。
表38.10 ハッシュサポート関数
関数 | サポート番号 |
---|---|
キーの32ビットハッシュ値を計算 | 1 |
64bitソルトが与えられたキーに対する64ビットハッシュ値を計算します。 ソルトが0なら結果の下位32ビットは関数1で計算された値と一致しなければなりません(省略可能) | 2 |
この演算子クラスに固有のオプションを定義します(省略可能) | 3 |
表 38.11に示すように、GiSTインデックスには11のサポート関数があり、また、そのうち6つは省略可能です。 (詳細については第68章を参照してください。)
表38.11 GiSTサポート関数
関数 | 説明 | サポート番号 |
---|---|---|
consistent | キーが問い合わせ条件を満たすかどうかを決定します | 1 |
union | キー集合の和集合を計算します | 2 |
compress | キーまたはインデックス付けされる値の圧縮表現を計算します(省略可能) | 3 |
decompress | 圧縮されたキーを伸張した表現を計算します(省略可能) | 4 |
penalty | 指定された副ツリーキーを持つ副ツリーに新しいキーを挿入する時のペナルティを計算します | 5 |
picksplit | ページのどのエントリを新しいページに移動させるかを決定し、結果ページ用の統合キーを計算します | 6 |
same | 2つのキーを比較し、等しければ真を返します | 7 |
distance | キーと問い合わせ値との間の距離を決定します(省略可能) | 8 |
fetch | インデックスオンリースキャンのために圧縮されたキーの元の表現を計算します(省略可能) | 9 |
options | この演算子クラスに固有のオプションを定義します(省略可能) | 10 |
sortsupport | 高速インデックス構築で使用されるソート比較を提供する(省略可能) | 11 |
表 38.12に示すように、SP-GiSTインデックスでは6つのサポート関数があり、また、そのうち1つは省略可能です。 (詳細については第69章を参照してください。)
表38.12 SP-GiSTサポート関数
関数 | 説明 | サポート番号 |
---|---|---|
config | 演算子クラスに関する基本情報を提供します | 1 |
choose | 新しい値を内部タプルに挿入する方法を決定します | 2 |
picksplit | 値集合を分割する方法を決定します | 3 |
inner_consistent | ある問い合わせでサブパーティションの検索が必要かどうか決定します | 4 |
leaf_consistent | キーが問い合わせ修飾子を満たすかどうか決定します | 5 |
options | この演算子クラスに固有のオプションの集合を定義します(省略可能) | 6 |
表 38.13に示すように、GINインデックスには、7つのサポート関数があり、また、そのうち4つは省略可能です。 (詳細については第70章を参照してください。)
表38.13 GINサポート関数
関数 | 説明 | サポート番号 |
---|---|---|
compare | 2つのキーを比較し、0未満、0、0より大きな整数を返します。 それぞれ最初のキーの方が大きい、等しい、小さいを示します | 1 |
extractValue | インデックス付けされる値からキーを抽出します | 2 |
extractQuery | 問い合わせ条件からキーを抽出します | 3 |
consistent | 問い合わせ条件に一致する値かどうかを決定します(2値の亜種)。 (サポート関数6があれば、省略可能) | 4 |
comparePartial | 問い合わせからの部分キーとインデックスからのキーを比較し、それぞれ、GINがこのインデックス項目を無視しなければならないか、一致する項目として扱わなければならないか、インデックススキャンを中止しなければならないかを示す、ゼロより小さい、ゼロ、ゼロより大きい整数値のいずれかを返します(省略可能) | 5 |
triConsistent | 問い合わせ条件に一致する値かどうかを決定します(3値の亜種)。 (サポート関数4があれば、省略可能) | 6 |
options | この演算子クラスに固有のオプションの集合を定義します(省略可能) | 7 |
表 38.14に示すようにBRINインデックスには、5つの基本サポート関数があり、また、そのうち1つは省略可能です。 基本関数の版には追加のサポート関数の提供を要求するものもあります。 (詳細については71.3を参照してください。)
表38.14 BRINサポート関数
関数 | 説明 | サポート番号 |
---|---|---|
opcInfo | インデックスが貼られた列の要約データを記述する内部情報を返します | 1 |
add_value | 既存のサマリインデックスタプルに新しい値を足します | 2 |
consistent | 値が問い合わせ条件に一致するかどうかを決めます | 3 |
union | 2つのサマリタプルの結合を計算します | 4 |
options | この演算子クラスに固有のオプションの集合を定義します(省略可能) | 5 |
検索演算子と異なり、サポート関数は特定のインデックスメソッドが想定するデータ型、例えばB-tree用の比較関数の場合、符号付き整数を返します。 同様に各サポート関数に渡す引数の数と型はインデックスメソッドに依存します。 B-treeとハッシュでは、比較関数とハッシュ処理サポート関数はその演算子クラスに含まれる演算子と同じ入力データ型を取りますが、GIN、SP-GiST、GiST、およびBRINサポート関数のほとんどはそうではありません。
ここまでで概念について説明してきました。
ここで、新しい演算子クラスを作成する有用な例を紹介します。
(この例を作業できるように、ソース配布物内のsrc/tutorial/complex.c
とsrc/tutorial/complex.sql
にコピーがあります。)
この演算子クラスは、複素数をその絶対値による順番でソートする演算子をカプセル化します。
ですので、その名前にcomplex_abs_ops
を選びました。
最初に演算子の集合が必要になります。
演算子を定義する処理は38.14で説明しました。
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
);
正しく交代演算子と否定演算子を指定する他、適切な制限選択性関数と結合関数を指定することが重要です。さもないと、オプティマイザはインデックスを効率的に使用することができません。
他にも注意すべきことがここで発生します。
例えば、complex
型を両オペランドに取る=
という名前の演算子を1つしか作成できません。
この場合、complex
用の他の=
演算子を持てません。
しかし、実際にデータ型を作成しているとしたら、おそらく、複素数の(絶対値の等価性ではない)通常の等価性演算を行う=
を欲するでしょう。
この場合、complex_abs_eq
用の演算子名に別の名前を使用しなければなりません。
PostgreSQLでは異なる引数のデータ型であれば同じSQL名の演算子を使うことができますが、Cでは1つの名前で1つのグローバル関数が使えるだけです。
ですから、C関数はabs_eq
のような単純な名前にするべきではありません。
通常は、他のデータ型の関数と衝突しないように、C関数名にデータ型名を入れておくことを勧めます。
abs_eq
関数のSQL名は、PostgreSQLが引数のデータ型によって同じ名前を持つ他のSQL関数から区別してくれることを期待して作ることができます。
ここでは例を簡単にするために、関数にCレベルとSQLレベルで同じ名前を与えています。
次のステップは、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
という単語を取り除いてください。
これまでは暗黙的に、演算子クラスは1つのデータ型のみを扱うものと仮定してきました。 確かに特定のインデックス列にはたった1つのデータ型しかあり得ませんが、異なるデータ型の値とインデックス列の比較を行うインデックス操作はよく役に立ちます。 また、演算子クラスと関連したデータ型を跨る演算子を使用できる場合、他のデータ型は独自の関連した演算子クラスを持つことがよくあります。 SQL問い合わせを最適化する際にプランナを補助することができますので、関連したクラスを明示的に関連付けることは(どのように動作するかに関する知識をプランナは多く持ちますので、特にB-tree演算子クラスで)有用です。
こうした要望に応えるためにPostgreSQLは演算子族という概念を使用します。 演算子族は1つ以上の演算子クラスから構成されます。 また、演算子族全体に属するが、演算子族内の個々のクラスには属さないインデックス可能演算子や対応するサポート関数を含めることもできます。 こうした演算子や関数を、特定のクラスに束縛されていないことから、演算子族内で「自由」であると呼びます。 通常、各演算子クラスは1つのデータ型演算子を持ちますが、データ型を跨る演算子は演算子族内で自由になります。
演算子族内の演算子と関数はすべて、意味的な互換性を持たなければなりません。 この互換性についての必要条件はインデックスメソッドによって設定されます。 このため、演算子族の特定の部分集合を演算子クラスとして選び出す方法に疑問を持つかもしれません。 実際多くの目的では、クラスの分類は不適切で、演算子族が唯一の興味深いグループ化です。 演算子クラスを定義する理由は、どれだけ多くの演算子族が何らかのインデックスをサポートするために必要かを指定することです。 ある演算子クラスを使用するインデックスが存在する場合、演算子クラスはそのインデックスを削除しない限り削除することができません。 しかし、演算子族の他の部分、すなわち、他の演算子クラスや自由な演算子を削除することができます。 したがって、演算子クラスは、特定のデータ型に対するインデックスを操作する上で理論上必要となる最少の演算子と関数の集合を含むように指定すべきです。 そして、関連するが基本的なものではない演算子を演算子族の自由なメンバとして追加することができます。
例えばPostgreSQLにはinteger_ops
という組み込みのB-tree演算子族があります。
ここにはbigint
(int8
)、integer
(int4
)、smallint
(int2
)型の列上へのインデックスにそれぞれ対応したint8_ops
、int4_ops
、int2_ops
という演算子クラスが含まれています。
また、上記の型の内任意の2つの型を比較できるように、この演算子族にはデータ型を跨る比較演算子も含まれます。
このため、上記の型のいずれかに対するインデックスを他の型の値との比較の際に使用することができます。
この演算子族は以下の定義により多重化されています。
CREATE OPERATOR FAMILY integer_ops USING btree; CREATE OPERATOR CLASS int8_ops DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS -- standard int8 comparisons -- 標準int8比較 OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint8cmp(int8, int8) , FUNCTION 2 btint8sortsupport(internal) , FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; CREATE OPERATOR CLASS int4_ops DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS -- standard int4 comparisons -- 標準int4比較 OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint4cmp(int4, int4) , FUNCTION 2 btint4sortsupport(internal) , FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; CREATE OPERATOR CLASS int2_ops DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS -- standard int2 comparisons -- 標準int2比較 OPERATOR 1 < , OPERATOR 2 <= , OPERATOR 3 = , OPERATOR 4 >= , OPERATOR 5 > , FUNCTION 1 btint2cmp(int2, int2) , FUNCTION 2 btint2sortsupport(internal) , FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) , FUNCTION 4 btequalimage(oid) ; ALTER OPERATOR FAMILY integer_ops USING btree ADD -- cross-type comparisons int8 vs int2 -- 型を跨ぐ比較 int8対int2 OPERATOR 1 < (int8, int2) , OPERATOR 2 <= (int8, int2) , OPERATOR 3 = (int8, int2) , OPERATOR 4 >= (int8, int2) , OPERATOR 5 > (int8, int2) , FUNCTION 1 btint82cmp(int8, int2) , -- cross-type comparisons int8 vs int4 -- 型を跨ぐ比較 int8対int4 OPERATOR 1 < (int8, int4) , OPERATOR 2 <= (int8, int4) , OPERATOR 3 = (int8, int4) , OPERATOR 4 >= (int8, int4) , OPERATOR 5 > (int8, int4) , FUNCTION 1 btint84cmp(int8, int4) , -- cross-type comparisons int4 vs int2 -- 型を跨ぐ比較 int4対int2 OPERATOR 1 < (int4, int2) , OPERATOR 2 <= (int4, int2) , OPERATOR 3 = (int4, int2) , OPERATOR 4 >= (int4, int2) , OPERATOR 5 > (int4, int2) , FUNCTION 1 btint42cmp(int4, int2) , -- cross-type comparisons int4 vs int8 -- 型を跨ぐ比較 int4対int8 OPERATOR 1 < (int4, int8) , OPERATOR 2 <= (int4, int8) , OPERATOR 3 = (int4, int8) , OPERATOR 4 >= (int4, int8) , OPERATOR 5 > (int4, int8) , FUNCTION 1 btint48cmp(int4, int8) , -- cross-type comparisons int2 vs int8 -- 型を跨ぐ比較 int2対int8 OPERATOR 1 < (int2, int8) , OPERATOR 2 <= (int2, int8) , OPERATOR 3 = (int2, int8) , OPERATOR 4 >= (int2, int8) , OPERATOR 5 > (int2, int8) , FUNCTION 1 btint28cmp(int2, int8) , -- cross-type comparisons int2 vs int4 -- 型を跨ぐ比較 int2対int4 OPERATOR 1 < (int2, int4) , OPERATOR 2 <= (int2, int4) , OPERATOR 3 = (int2, int4) , OPERATOR 4 >= (int2, int4) , OPERATOR 5 > (int2, int4) , FUNCTION 1 btint24cmp(int2, int4) , -- cross-type in_range functions FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) , FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) , FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;
この定義は演算子ストラテジ関数番号とサポート関数番号を「上書き」していることに注意してください。 各番号は演算子族内で複数回現れます。 特定番号のインスタンスがそれぞれ異なる入力データ型を持つ限り、これは許されます。 入力型の両方が演算子クラスの入力型と同じインスタンスは、演算子クラスの主演算子および主サポート関数であり、ほとんどの場合、演算子族の自由メンバではなく演算子クラスの一部として宣言しなければなりません。
詳細が67.2で示されている通り、B-tree演算子族では演算子族内のすべての演算子は互換性をもってソートしなければなりません。 演算子族内の各演算子では、演算子と同じデータ型の2つのデータ型を取るサポート関数が存在しなければなりません。 演算子族を完結させること、つまり、データ型の組み合わせそれぞれに対する演算子をすべて含めることを推奨します。 各演算子クラスは、自身のデータ型に対してデータ型を跨らない演算子とサポート関数だけを含めなければなりません。
複数データ型のハッシュ演算子族を構築するには、演算子族でサポートされるデータ型それぞれに対する互換性を持つハッシュサポート関数を作成しなければなりません。 ここで、互換性とは、関数がその演算子族の等価性演算子で等価であるとみなされる任意の2つの値では同一のハッシュコードが生成されることを保証することを意味します。 通常、型が異なる物理表現を持つ場合、これを実現することは困難ですが、実現可能な場合もあります。 さらに、暗黙的またはバイナリ変換により、ある演算子族で表現されるデータ型から同じ演算子族で表現されるデータ型に値をキャストしても、計算されたハッシュ値を変更してはいけません。 データ型1つに対してサポート関数が1つしか存在しないことに注意してください。 等価性演算子ごとに1つではありません。 演算子族を完結させること、つまり、データ型の組み合わせそれぞれに対する等価性演算子をすべて含めることを推奨します。 各演算子クラスは、自身のデータ型に対してデータ型を跨らない演算子とサポート関数だけを含めなければなりません。
GiST、SP-GiST、GINインデックスではデータ型を跨る操作についての明示的な記法はありません。 サポートされる演算子群は単に指定演算子クラスの主サポート関数が扱うことができるものです。
BRINでは、要求は演算子クラスを提供するフレームワークに依存します。
minmax
に基づく演算子クラスに対しては、求められる振る舞いはB-tree演算子クラスに対するものと同じです。族内のすべての演算子はソート互換でなければならず、キャストは関連するソート順序を変更してはいけません。
PostgreSQL8.3より前のバージョンでは演算子族という概念はありませんでした。 そのため、インデックスで使用する予定のデータ型を跨る演算子はすべて、インデックスの演算子クラスに結びつけなければなりませんでした。 この手法もまだ使用できますが、インデックスの依存性を広げる点、および、両データ型が同一演算子族内で演算子を持つ場合、プランナがデータ型を跨った比較をより効率的に扱うことができる点より、廃止予定です。
PostgreSQLは演算子クラスを、単にインデックスで使用できるかどうかだけではなく、多くの方式で演算子の性質を推定するために使用します。 したがって、データ型の列をインデックス付けするつもりがなくても、演算子クラスを作成した方が良い可能性があります。
具体的には、ORDER BY
やDISTINCT
など、値の比較とソートを必要とするSQL機能があります。
ユーザ定義のデータ型に対してこの機能を実装するために、PostgreSQLはそのデータ型用のデフォルトのB-tree演算子クラスを検索します。
この演算子クラスの「等価判定」メンバが、GROUP BY
やDISTINCT
用の値の等価性についてのシステムの意向を定義し、この演算子クラスによって強制されるソート順序が、デフォルトのORDER BY
順序を定義します。
データ型用のデフォルトのB-tree演算子クラスが存在しないと、システムはデフォルトのハッシュ演算子クラスを検索します。 しかし、この種類の演算子クラスは等価性のみを提供しますので、ソートではなくグループ化のみサポートできます。
データ型用のデフォルトの演算子クラスが存在しない場合に、こうしたSQL機能をデータ型に使用しようとすると、「順序付け演算子を識別できなかった」といったエラーとなります。
PostgreSQLバージョン7.4より前まででは、ソートやグループ化演算は暗黙的に=
、<
、>
という名前の演算子を使用していました。
この新しい、デフォルトの演算子クラスに依存する振舞いによって、特定の名前を持つ演算子の振舞いについて何らかの仮定を立てることを防止しています。
演算子クラスの小なり演算子をUSING
オプションに指定することで、デフォルトでないB-tree演算子クラスによるソートが可能です。
以下に例を示します。
SELECT * FROM mytable ORDER BY somecol USING ~<~;
代わりにUSING
で演算子クラスの大なり演算子を指定すると降順ソートが行われます。
ユーザ定義型の配列の比較も型のデフォルトB-tree演算子クラスで定義された意味に依存します。 デフォルトのB-tree演算子クラスが無く、しかしデフォルトのハッシュ演算子クラスがある場合、配列の順比較ではなく同等比較がサポートされます。
データ型特有の知識をさらに必要とする他のSQL仕様としては、ウィンドウ関数(4.2.8を参照してください)のRANGE
offset
PRECEDING
/FOLLOWING
フレームオプションがあります。
下記のような問い合わせに対して、
SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) FROM mytable;
これはどのようにx
で整列するかを知るのに十分ではありません。
データベースは現在のウィンドウフレームの境界を識別するためにどのように現在行のx
の値に「5を減算」や「10を加算」を行うかを理解する必要もあります。
ORDER BY
整列を定義するB-tree演算子クラスで提供される比較演算子を使って結果として生じる他の行のx
値への範囲を比較することは可能です。
しかし、加算、減算演算子は演算子クラスの一部ではありません。では、どの演算子が使われるべきでしょうか。
異なるソート順序(異なるB-tree演算子クラス)では異なる振る舞いを要するかもしれないため、選択を決め打ちすることは望ましくありません。
そのため、B-tree演算子クラスはそのソート順に意味がある加算と減算の振る舞いをカプセル化するin_rangeサポート関数を指定することができます。
RANGE
句のオフセットとして使う意味のある複数のデータ型がある場合にむけて、複数のin_rangeサポート関数を提供することもできます。
ウィンドウのORDER BY
句と関連しているB-tree演算子クラスが、一致するin_rangeサポート関数を持たない場合、RANGE
offset
PRECEDING
/FOLLOWING
オプションはサポートされません。
他の重要な点として、ハッシュ演算子族内に現れる等価性演算子がハッシュ結合、ハッシュ集約、関連する最適化の候補となることがあります。 使用するハッシュ関数を識別するため、ここでのハッシュ演算子族は基本的なものです。
一部のインデックスアクセスメソッド(現時点ではGiSTとSP-GiSTのみ)は順序付け演算子という概念をサポートします。
これまで説明してきたものは検索演算子でした。
検索演算子は、WHERE
indexed_column
operator
constant
を満たすすべての行を見つけるために、インデックスを検索可能にするためのものです。
一致した行がどの順序で返されるかについては保証がないことに注意してください。
反対に、順序付け演算子は返すことができる行集合を限定しませんが、その順序を決定します。
順序付け演算子は、ORDER BY
indexed_column
operator
constant
で表される順序で行を返すために、インデックスをスキャン可能にするためのものです。
このように順序付け演算子を定義する理由は、その演算子が距離を測るものであれば最近傍検索をサポートすることです。
例えば以下のような問い合わせを考えます。
SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
これは指定した対象地点に最も近い10地点を見つけ出します。
<->
は順序付け演算子ですので、location列上のGiSTインデックスは、これを効率的に行うことができます。
検索演算子が論理値結果を返さなければなりませんが、順序付け演算子は普通、距離を表す浮動小数点や数値型など、何らかの他の型を返します。
この型は通常、インデックス対象のデータ型と同じにはなりません。
異なるデータ型の動作についての固定化された前提を防ぐために、順序付け演算子の定義では、結果データ型のソート順序を指定するB-tree演算子族の名前を必要とします。
前節で述べたように、B-tree演算子族はPostgreSQLの順序付け記法を定義します。
ですのでこれは自然な表現です。
pointに対する<->
演算子はfloat8
を返しますので、演算子クラスを作成するコマンド内で以下のように指定します。
OPERATOR 15 <-> (point, point) FOR ORDER BY float_ops
ここでfloat_ops
は、float8
に対する操作を含んだ組み込みの演算子族です。
この宣言は、インデックスが<->
演算子の値が増加する方向で行を返すことができることを表しています。
演算子クラスには、まだ説明していない2つの特殊な機能があります。 説明していない主な理由は、最もよく使用するインデックスメソッドでは、これらがあまり有用ではないためです。
通常、演算子を演算子クラス(または演算子族)のメンバとして宣言すると、インデックスメソッドでその演算子を使用して、WHERE
条件を満たす行の集合を正確に抽出することができます。
以下に例を示します。
SELECT * FROM table WHERE integer_column < 4;
この式は、整数列にB-treeインデックスを使用することにより、正確に満たすことができます。
しかし、一致する行へ厳密ではなくとも導く手段としてインデックスが有用である場合があります。
例えば、GiSTインデックスで、幾何オブジェクトの外接矩形のみを格納したとします。
その結果、多角形のような長方形でないオブジェクトとの重なりをテストするWHERE
条件は正確に満たすことができません。
もっとも、このインデックスを使用して、対象オブジェクトの外接矩形に重なる外接矩形を持つオブジェクトを検索し、さらに、検索されたオブジェクトのみに対して正確に重なるかどうかをテストすることはできます。
この筋書きを適用する場合、インデックスは演算子に対して「非可逆」と言われます。
非可逆インデックス検索は、ある行が問い合わせ条件を実際に満足するかしないかの時にrecheckフラグを返すインデックスメソッドを持つことで実装されます。
コアシステムは、そこで有効なマッチとして行が返されるか否かを確認するために、抽出された行に対して元の問い合わせ条件を検査します。
この手法はインデックスがすべての必要な行を返すことが保証された上で、元の演算子呼び出しを実行することによって除外することができる、いくつか余分な行を返す可能性がある場合に動作します。
非可逆検索を提供するインデックス方式(現時点ではGiST、SP-GiSTおよびGIN)は個々の演算子クラスのサポート関数がrecheckフラグを設定することを許可します。
このためこれは原則的に演算子クラスの機能です。
再度、多角形のような複雑なオブジェクトの外接矩形のみをインデックスに格納している状況を考えてみてください。
この場合、インデックスエントリに多角形全体を格納するのは、それほど有用なことではありません。
単に、より単純なbox
型のオブジェクトを格納した方が良いかもしれません。
このような状況は、CREATE OPERATOR CLASS
のSTORAGE
オプションによって表現することができます。
例えば、以下のように記述します。
CREATE OPERATOR CLASS polygon_ops DEFAULT FOR TYPE polygon USING gist AS ... STORAGE box;
現時点では、GiST、SP-GiST、GINおよびBRINインデックスメソッドが、列のデータ型と異なるSTORAGE
型をサポートしています。
STORAGE
が使用された場合、GiSTのcompress
およびdecompress
サポートルーチンは、データ型を変換する必要があります。
SP-GiSTも同様に、STORAGE型が異なる場合に変換するためのcompress
サポート関数を必要とします。
SP-GiST演算子クラスがデータの取得もサポートする場合、逆変換はconsistent
関数で処理する必要があります。
GINでは、STORAGE
型は「キー」の値の型を識別します。
通常これはインデックス付けされる列の型とは異なります。
例えば、整数配列の列用の演算子クラスは単なる整数をキーとして持つかもしれません。
GINのextractValue
およびextractQuery
サポートルーチンが、インデックス付けされた値からキーを取り出す責任を負います。
BRINはGINと同様です。STORAGE
型は格納された要約値の型を識別し、演算子クラスのサポートプロシージャは要約値を正しく解釈する責任を負います。