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

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

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

pg_am クラスには、ユーザ定義の各アクセス メソッド用のインスタンスが一つずつ含まれています。 Postgres にはヒープアクセスメソッドが組み込まれ ていますが、他のすべてのアクセスメソッドはこのクラスに記述されて います。スキーマは以下の通りです。

Table 9-1. インデックスのスキーマ

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

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

SELECT oid FROM pg_am WHERE amname = 'btree';

         +----+
         |oid |
         +----+
         |403 |
         +----+
   
後の WHERE 節内にてこの SELECT を使います。

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

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

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

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

考え方としては、上述の比較方法に対応する処理を、 pg_amop リレーション(後述)に追加する 必要があるということです。 B-tree をどう分 割するか、選択性をどう計算するかなどを見つけるために、アクセ スメソッドのコードは、データ型にかかわらずこれらのストラテジ 番号を使用することができます。処理を追加するための具体的な方 法についてはまだ気にかけることはありません。 B-tree が操作できる int2, int4, oid やその他のデータ型には、これらの処理が必要であるということ を理解できればよいのです。

ストラテジがシステムに対してインデックスを使う方法を見つけるのに十分な 情報を持っていない場合もあります。アクセスメソッドの中には、その動作に その他のサポートルーチンが必要なものもあります。たとえば B-tree アクセスメソッドは、2つのキーを比較し、 より大きいのか、等しいのか、より小さいのかを決定できなければ なりません。同様に、 R-tree アクセスメソッドは 四角形の共通部分、和集合、交差の度合などを計算できなければなりません。 これらの操作は SQL クエリーにおけるユーザが指定した条件と は対応しません。これらは内部的にアクセスメソッドが使用する 管理ルーチンです。

すべての Postgres アクセスメソッドに対して 様々なサポートルーチンを整合的に管理するために、pg_am amsupport と呼ばれる属性を 含んでいます。この属性は、1つのアクセスメソッドから使われるルー チンの数を持ちます。 B-tree においてはこの数は 1 です。このルーチンは2つのキーを引数とし、最初のキーが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 は他のクラスにおける外部キーとなりま す。

INSERT INTO pg_opclass (opcname, opcdeftype)
    SELECT 'complex_abs_ops', oid FROM pg_type WHERE typname = 'complex_abs';

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

         +------+-----------------+------------+
         |oid   | opcname         | opcdeftype |
         +------+-----------------+------------+
         |17314 | complex_abs_ops |      29058 |
         +------+-----------------+------------+
   
システムによって pg_opclass インスタンスの oid は 上の値とは異なります。このことは気にしないでください。ここで型の oid として得たこの値を、この後でもシステムから入手した値として使用します。

これでアクセスメソッドと演算子クラスができました。次に、演算子の セットが必要になります。演算子を定義する処理はこのマニュアル で前述しました。 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.source をご覧ください。)

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

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

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

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

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

同様に使いたい型を引数にとる演算子名も分かります。 ここで大事なことは、procedure(上の場合は C の関数) と restrict と join 選択関数です。あなたは後述において使われている ものを使うべきです -- 注意:「より小さい」、「等しい」、「より大きい」 のそれぞれのケースにおいて、これらの関数が異なることに注意して下さい。 これらが提供されないと、アクセスメソッドがその演算子を使おうとすると クラッシュしてします。restrict と join の名前はコピーするべき ですが、 procedure 名には前のステップで定義したものを使って下さい。

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

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

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

Table 9-3. pg_amproc スキーマ

属性説明
amopid B-Tree用の pg_am インスタンス のoid(== 403, 前述)
amopclaid complex_abs_ops 用の pg_opclass インスタンスの oid(17314の代わりに得たもの。前述)
amopopr opclassの演算子のoid。(すぐ後に取得します。)
amopselect, amopnpagesコスト関数
コスト関数は、クエリーオプティマイザが与えられたインデックスを スキャンで使うかどうかを決定するために使われます。幸いなことに、 これらはすでにあります。 B-tree を使うかどうかを評価する btreesel と、 ツリー上で検索ルーチンが使うページ数を予測するための btreenpage の2つの関数を使います。

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

    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_abs';

         +------+---------+
         |oid   | oprname |
         +------+---------+
         |17321 | <       |
         +------+---------+
         |17322 | <=      |
         +------+---------+
         |17323 |  =      |
         +------+---------+
         |17324 | >=      |
         +------+---------+
         |17325 | >       |
         +------+---------+
   
(ここで再び。システムによって oid 番号はほと んど確実に異なります。) ここで知りたい演算子は oid が17321から17325までのものです。あなたが得た値はおそ らく異なっているので、以下ではあなたの得た値に置き換えて考えて ください。本書ではこの値で select 文を使っていきます。

これで新しい演算子クラスで pg_amop を更新す る準備ができました。本節全体で最も大切なことは、pg_amop の中では演算子は、「等しいかより小さい」、から 「等しいかより大きい」までが順番に並んでいるということです。必要 なインスタンスを追加してみます。

    INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy,
                amopselect, amopnpages) 
        SELECT am.oid, opcl.oid, c.opoid, 1,
                'btreesel'::regproc, 'btreenpage'::regproc
        FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c
        WHERE amname = 'btree' AND
            opcname = 'complex_abs_ops' AND
            c.oprname = '<';
   
他の演算子については上の3行目の "1" と最後の "<" を置き換え てインスタンスを追加します。注意: 順番は"より小さい"が 1 、 "等しいかより小さい"が 2 、"等しい"が 3 、"等しいかより大きい" が 4 、"より大きい"が 5 です。

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

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

    SELECT oid, proname FROM pg_proc
     WHERE proname = 'complex_abs_cmp';

         +------+-----------------+
         |oid   | proname         |
         +------+-----------------+
         |17328 | complex_abs_cmp |
         +------+-----------------+
   
(再び。システムによって入手する oid 番号はおそらく 異なっていますので、以降ではあなたが得た値に置き換えてください。) 次のように新しいインスタンスを追加できます。
    INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum)
        SELECT a.oid, b.oid, c.oid, 1
            FROM pg_am a, pg_opclass b, pg_proc c
            WHERE a.amname = 'btree' AND
                b.opcname = 'complex_abs_ops' AND
                c.proname = 'complex_abs_cmp';
   

ここで、その型についてインデックスを作成できるようにハッシュ ストラテジを追加する必要があります。pg_am 内の他の型を使用して 行っていますが、同じ演算子を再利用します。

    INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy,
                amopselect, amopnpages)
        SELECT am.oid, opcl.oid, c.opoid, 1,
                'hashsel'::regproc, 'hashnpage'::regproc
        FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c
        WHERE amname = 'hash' AND
            opcname = 'complex_abs_ops' AND
            c.oprname = '=';
   

このインデックスを where 節で使用するために、pg_operator クラスを次のように変更します。

    UPDATE pg_operator
        SET oprrest = 'eqsel'::regproc, oprjoin = 'eqjoinsel'
        WHERE oprname = '=' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel'
        WHERE oprname = '' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel'
        WHERE oprname = '' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'intltsel'::regproc, oprjoin = 'intltjoinsel'
        WHERE oprname = '<' AND 
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'intltsel'::regproc, oprjoin = 'intltjoinsel'
        WHERE oprname = '<=' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'intgtsel'::regproc, oprjoin = 'intgtjoinsel'
        WHERE oprname = '>' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
    
    UPDATE pg_operator
        SET oprrest = 'intgtsel'::regproc, oprjoin = 'intgtjoinsel'
        WHERE oprname = '>=' AND
            oprleft = oprright AND
            oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
   

最後に(最後です!)この型についての説明文を登録します。

    INSERT INTO pg_description (objoid, description) 
    SELECT oid, 'Two part G/L account'
	    FROM pg_type WHERE typname = 'complex_abs';