PostgreSQL
PrevNext

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

これまでのところでは、新しい型や新しい関数、および新しい演算子 について説明してきました。しかしながら、新しい型やその演算子に 対する(B-tree, R-tree やハッシュアクセスメソッドなどを使った)2次インデックスについては 説明していませんでした。

The major Postgres system catalogs をもう一度読み返してみてください。ここのページの右半分では、 ユーザ定義の型および/またはインデックス付きのユーザ定義演算子 (すなわちpg_am, pg_amop, pg_amprocおよび pg_opclass)の使い方を Postgresに対して伝えるために、 変更する必要のあるカタログに関してご紹介しています。 残念ながら、これらのことを行うためのシンプルなコマンドは存在しません。 ここでは、これらのカタログを変更するやりかたについて、 整数を値の絶対値の昇順にソートする、B-tree アクセスメソッドを使った新しい演算子クラスを作るという例を挙げて ご紹介します。

pg_amクラスには、ユーザ定義の各アクセスメソッド ごとの1つのインスタンスが含まれています。 Postgresにはヒープアクセスメソッドが 組み込まれていますが、他のすべてのアクセスメソッドをここでご紹介 します。概要としては以下の通りです。

Table 36-1. インデックスの概要

属性説明
amnameアクセスメソッドの名前
amownerpg_user における、所有者のインスタンスのオブジェクトID(oid)
amkind現在は使われていないが、場所を確保するために(as a place holder) 'o' をセットする
amstrategiesこのアクセスメソッド用のストラテジの数(後述)
amsupportこのアクセスメソッド用のサポートルーチンの数(後述)
amgettuple aminsert ... そのアクセスメソッドへのインターフェースルーチンのための プロシージャ識別子。たとえば regproc id は、ここでご紹介する アクセスメソッドをオープン/クローズしたり、そこからインスタンス を取得したりするためのものです。

pg_amにおけるインスタンスの オブジェクト識別子(oid)は、その他多くのクラスにおいて代替キー として使用されます。このクラスに新しいインスタンスを追加する必要は ありません。必要なのは、拡張したいアクセスメソッドのインスタンスの オブジェクト識別子(oid)だけです。

SELECT oid FROM pg_am WHERE amname = 'btree';

         +----+
         |oid |
         +----+
         |403 |
         +----+

amstrategies属性は、データ型をまたがる比較処理 を標準化するためにあります。たとえばB-treeの場合、 キーが小さい方から大きい方へ厳密に並んでいなければなりません。 Postgresではユーザが演算子を定義できるので、 Postgresは演算子(たとえば ">" や "<")の 名前を見つけることができず、その演算子に対してどのような比較を行えば 良いかを指示することができません。実際、アクセスメソッドによっては まったく順序性を規定しないものもあります。たとえばR-tree は四角形に閉じた関係を表しますが、ハッシュデータはハッシュ 関数の値によってビット毎の類似性を表しているだけです。 Postgresは、クエリーを実行する際に 何らかの一貫性のあるデータの限定方法を必要とするため、演算子を見つけて、 使用できるインデックスが存在するかどうかを決定します。ということは、 Postgresは、たとえばB-tree を区切る "<=" や ">" 演算子を知っている必要があることになります。 Postgresは、演算子と、インデックスをスキャン するための方法の間のこれらの関係を、ストラテジを使って表現します。

新しいストラテジ・セットの定義はここの議論の範囲ではありませんが、 新しい演算子クラスを追加するために必要なので、ここでは B-treeストラテジがどういうふうに動作するか を説明することにします。pg_amクラスにおいて amstrategies 属性は、このアクセスメソッドのために定義された ストラテジの数です。B-treeにおいてはこの数は 5 です。これらのストラテジは、以下のものに対応しています。

Table 36-2. B-tree ストラテジ

オペレーションインデックス
より小さい1
より小さいか等しい2
等しい3
より大きいか等しい4
より大きい5

考え方としては、比較方法に対応する処理を、上記のpg_amop リレーション(後述)に追加する必要があるということです。 B-treeをどう分割するか、選択性をどう計算するか などを見つけるために、アクセスメソッドのコードは、データ型に かかわらずこれらのストラテジ番号を使用することができます。 処理を追加するための具体的な方法についてはまだ気にかけることは ありません。B-treeが操作できる int2, int4, oidその他のデータ型のために、これらの 手続きが必要であるということが理解できればよいのです。 ストラテジは、システムがインデックスを使う方法を見つけるのに十分な 情報を持っていない場合もあります。アクセスメソッドの中には、動作する ためにその他のサポートルーチンが必要なものもあります。たとえば B-treeアクセスメソッドでは2つのキーを比較し、 より大きいのか、等しいのか、より小さいのかなどを決定できなければ なりません。同様に、R-treeアクセスメソッドは 4角形の交差、ユニオン、サイズなどを計算できなければなりません。 これらの操作は SQL ステートメントにおけるユーザの限定とは対応しません。 これらは内部的にアクセスメソッドから使用される管理ルーチンです。

すべてのPostgresアクセスメソッドに対して 様々なサポートルーチンを継続的に管理するために、pg_am amsupportと呼ばれる属性を インクルードしています。この属性レコードでは、1つのアクセスメソッド から使われるルーチンの数を定義しています。B-tree においてはこの数は1です。このルーチンでは、最初のキーが2番目の キーより小さいか等しいか大きいかにより、それぞれ -1, 0, +1 を返します。

NOTE: 厳密に言うと、このルーチンは負数(< 0)、0、および0でない正数(> 0)を 返すことができます。

pg_am のamstrategiesエントリは、問い合わせに おけるアクセスメソッドのために定義されているストラテジの数に 過ぎません。より小さい、小さいか等しい、などの手続きは pg_amには現れません。同様に、 amsupportはアクセスメソッドが要求するサポートルーチンの 数に過ぎません。実際のルーチン群は別のところに定義されています。

次に重要なのは、pg_opclass クラスです。このクラスは名前と oid を 関連付けるためだけに存在します。pg_amop においては、すべての B-tree演算子クラスは、上記にに示したように、 1から5個の手続きのセットを持っています。存在する opclass としては、 int2_ops, int4_ops, oid_opsがあります。 あなたはこのインスタンスを、自分の opclass 名(たとえば complex_abs_ops)と共にpg_opclass に追加してやらなければなりません。このインスタンスのoid は他のクラスにおける代替キー(foreign key)となります。

INSERT INTO pg_opclass (opcname) VALUES ('complex_abs_ops');

SELECT oid, opcname
    FROM pg_opclass
    WHERE opcname = 'complex_abs_ops';

         +------+--------------+
         |oid   | opcname      |
         +------+--------------+
         |17314 | int4_abs_ops |
         +------+--------------+
注意:あなたのpg_opclassインスタンスのための oid は異なっていなければならなりません。これ以降の議論においては、 17314 をあなたの実際の値に読み替えて下さい。

これでアクセスメソッドと演算子クラスができました。次に、演算子の セットが必要になります。演算子を定義するための手続きはこの マニュアルで前述しました。Btrees 上の complex_abs_ops 演算子 については、以下のようにします。

        absolute value less-than
        absolute value less-than-or-equal
        absolute value equal
        absolute value greater-than-or-equal
        absolute value greater-than

定義されている関数を実装するコードは、 PGROOT/src/tutorial/complex.c に格納されています。

コードの一部を以下に示します。 (注意:この例の残りの部分では、イコール演算子のみを示すことにします。 他の4つの演算子についても良く似ています。詳細は、 complex.cまたはcomplex.sql をご覧ください。)

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

         bool
         complex_abs_eq(Complex *a, Complex *b)
         {
             double amag = Mag(a), bmag = Mag(b);
             return (amag==bmag);
         }

ここで重要なことが2つほどあります。

まず、int4用のless-than,less-than-or-equal, equal, greater-than-or-equal, greater-than のための演算子が定義 されようとしているということです。これらの演算子は、 int4のために、<, <=, =, >= ,> という名前で すでに定義されています。もちろん新しい演算子は異なった振る舞いを します。Postgresが古い演算子でなく これらの新しい演算子を使うことを保証するために、これらには古いもの とは異なった名前を付けなければなりません。ここが肝心です。 その演算子が引数型のために定義されていない場合にのみ、 Postgresで演算子をオーバーロードする ことができます。すなわち、(int4, int4) のための < がある場合、 それを再度定義することはできません。演算子を定義する際に、 Postgresはこの重複をチェックしないので 注意して下さい。この問題を避けるために、演算子には風変わりな名前が 使われます。これが守られないと、スキャンを実行する際に、 アクセスメソッドはクラッシュを起こしてしまうかもしれません。

もう1つの重要な点は、すべての演算子関数はブール値を返すという ことです。アクセスメソッドはこの事実を拠り所にしています。(一方、 サポート関数は特定のアクセスメソッドが期待するもの -- このケースでは 符号付き整数 -- を返します。) ファイル中の最後のルーチンは、pg_amクラスの amsupport 属性を議論した時に言及した "サポートルーチン" です。 これは後で使いますので、今は無視しておいてかまいません。

CREATE FUNCTION complex_abs_eq(complex, complex)
              RETURNS bool
              AS 'PGROOT/tutorial/obj/complex.so'
              LANGUAGE 'c';

ここでこれらを使用する演算子を定義します。注意したように、 演算子の名前は、2つのint4オペランドを取る すべての演算子の間で一意でなければなりません。後述する、定義済み 演算子名のリストを得るには、pg_operator に対してクエリーを発行します。

    /*
     * このクエリーは、正規表現演算子 (~) を使って、文字 & で
     * 終わる3文字の演算子名を見つけます。
     */
    SELECT *
     FROM pg_operator
     WHERE oprname ~ '^..&$'::text;

ここで大事なことは、手続き(上記の場合はCと同義語) と restrict と join 選択関数です。あなたは後述において使われている うちの1つだけを使うべきです -- 注意:less-than, equal, greater-than の ケースにおいて、このように異なった関数があることに注意して下さい。 これらが提供されないと、アクセスメソッドがその演算子を使おうとすると クラッシュします。restrict と join には名前をコピーするべき ですが、最後のステップでは定義した手続き名を使って下さい。

CREATE OPERATOR = (
     leftarg = complex, rightarg = complex,
     procedure = complex_abs_eq,
     restrict = eqsel, join = eqjoinsel
         )

注意:より小さい、小さいか等しい、等しい、より大きい、および より大きいか等しい、に対応した5つの演算子が定義されています。

さあ、もうすぐで終わりです。最後に、pg_amop リレーションを更新しなければなりません。これには以下の属性が 必要です。

Table 36-3. pg_amprocスキーマ

属性説明
amopidB-tree のためのpg_amインスタンスの oid(== 403、前述)
amopclaid

int4_abs_ops

pg_opclass

oid

17314

amopopr opclass のための演算子のoid (すぐに取得できる)
amopselect, amopnpagesコスト関数
コスト関数は、クエリーオプティマイザが、与えられたインデックスを スキャンで使うかどうかを決定するために使われます。幸いなことに、 これらはすでにあります。我々が使うものは、B-tree を使うかどうかを評価するbtreeselと、 ツリー上で検索ルーチンが使うページ数を予測するための btreenpageです。

このため、定義した演算子のoidが必要になります。 ここで、2つのint4を取るすべての演算子の名前 を検索し、その中から私たちのものを抽出してみます。

    SELECT o.oid AS opoid, o.oprname
     INTO TABLE complex_ops_tmp
     FROM pg_operator o, pg_type t
     WHERE o.oprleft = t.oid and o.oprright = t.oid
      and t.typname = 'complex';

         +------+---------+
         |oid   | oprname |
         +------+---------+
         |17321 | <       |
         +------+---------+
         |17322 | <=      |
         +------+---------+
         |17323 |  =      |
         +------+---------+
         |17324 | >=      |
         +------+---------+
         |17325 | >       |
         +------+---------+
(ここで再び。あなたのいくつかのoid番号は、 ほとんど確実に異なっていますね。)ここで知りたいのは、 oidが 17321 から 17325 までのものです。 あなたが得られるこれらの値はおそらく異なっているので、以下に示す 値に置き換えて考えてください。我々は、今追加したばかりの演算子の 名前を見つけて取り出すことができます。

ここで、私たちは新しい演算子クラスでpg_amop を更新する準備ができました。この議論全体の中で一番大切なことは、 pg_amopの中では、演算子は、小さいか等しい、 から大きいか等しいまで並んでいるということです。必要なインスタンス を追加してみます。

    INSERT INTO pg_amop (amopid, amopclaid,
     amopopr, amopstrategy,
     amopselect, amopnpages)
    SELECT am.oid, opcl.oid, c.opoid, 3,
     'btreesel'::regproc, 'btreenpage'::regproc
    FROM pg_am am, pg_opclass opcl, complex_ops_tmp c
    WHERE amname = 'btree'
     and opcname = 'complex_abs_ops'
     and c.oprname = '=';
注意:順序としては、"less than" は 1、"less than or equal" は 2、 "equal" は 3、"greater than or equal" は 4、"greater than" は 5 です。

最終ステップ(最後!)は、pg_amのところで 議論した、"サポートルーチン" の登録です。このサポートルーチンの oidpg_amprocクラスに 格納されており、アクセスメソッドのoidと 演算子クラスのoidがキーになっています。 最初に、Postgresにおいて関数を登録 する必要があります。 (私たちは、演算子ルーチンを実装したファイルの最後に、このルーチン を実装するCコードを置くことを思い出して下さい。

    CREATE FUNCTION int4_abs_cmp(int4, int4)
     RETURNS int4
     AS 'PGROOT/tutorial/obj/complex.so'
     LANGUAGE 'c';

    SELECT oid, proname FROM pg_proc
     WHERE prname = 'int4_abs_cmp';

         +------+--------------+
         |oid   | proname      |
         +------+--------------+
         |17328 | int4_abs_cmp |
         +------+--------------+
(再び。あなたのoid番号はおそらく異なっている でしょうから、後述の値に置き換えて考えてくださいね。) B-treeインスタンスの oid は 403 で、 int4_abs_opsのそれは 17314 であることを 思い出してください。以下のように新しいインスタンスを追加することが できます。
    INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum)
     VALUES ('403'::oid, -- btree oid
     '17314'::oid,       --  pg_opclass tuple
     '17328'::oid,       -- new pg_proc oid
     '1'::int2);


PrevHomeNext
PostgresルールシステムUpGiST Indices