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

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

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

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

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

説明
amnameアクセスメソッド名
amowner所有者のユーザ ID
amstrategiesこのアクセスメソッド用のストラテジの数(後述)
amsupportこのアクセスメソッド用のサポートルーチンの数(後述)
amorderstrategy インデックスがソート順を提供しない場合はゼロ、その他の場合は ソート順を説明するストラテジ演算子のストラテジ数
amgettuple 
aminsert 
... アクセスメソッドへのインターフェースルーチン 用のプロシージャ識別子。 たとえば regproc id は、ここで紹介するアクセスメソッドをオープン/ クローズしたり、そこからインスタンスを取得したりするためのものです。

pg_amの中の行のオブジェクト ID は他の沢山のテーブルの中で外部キーとして使われています。 このテーブルには新しい行を追加する必要はありません。唯一重要なのは 拡張したい行のアクセスメソッドのオブジェクト ID です。

SELECT oid FROM pg_am WHERE amname = 'btree';

 oid
-----
 403
(1 row)
   
後の WHERE 節内にてこの SELECT を使います。

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

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

Table 18-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_amamsupport と呼ばれる列を含んでいます。 この列は、1 つのアクセスメソッドから使われるルーチンの数を持ちます。 B-tree においてはこの数は 1 です。この ルーチンは2つのキーを引数とし、最初のキーが2番目のキーより小さいか等しい か大きいかにより、それぞれ -1, 0, +1 を返 します。

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

pg_amamstrategies 項目は、 対象としているアクセスメソッド用に定義されたストラテジの数に過 ぎません。 「より小さい」、「以下」か、といった処理は pg_am には現れません。同様に、 amsupport はアクセスメソッドが要求するサポー トルーチンの数に過ぎません。実際のルーチンは別のところに定義されています。

ところで、amorderstrategyの項目は アクセスメソッドが順スキャンをサポートするかどうかを示します。 ゼロはサポートしないことを意味します。サポートする場合は amorderstrategyが順番の演算子と対応する 戦略ルーチンの数になります。例えば、btree では amorderstrategy = 1 となっており、 これは戦略番号「より小さい」のです。

次に重要なテーブルは、 pg_opclass です。このテーブル は演算子クラス名(と、場合によってはデフォルト型)を、演算子クラスの oid と 関連付けるためだけに存在します。 既存の opclass として、 int2_ops, int4_ops, oid_ops があります。 自作の opclass 名(例えば complex_abs_ops )を持った 行を pg_opclass に追加しなくてはなりません。 この行の oid は他のテーブルにおける外部キー となり、とくに pg_amop は重要です。

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

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

  oid   |     opcname     | opcdeftype
--------+-----------------+------------
 277975 | complex_abs_ops |     277946
(1 row)
   
pg_opclass行の oid は違ったものであることに 注意して下さい! しかし心配することはありません。この番号は 型の oid を得たのと同じように後でシステムから得ることができます。

上記の例は、ユーザがこの新しい opclass をcomplex データ型のデフォルトインデックス opclass にしようとしていると仮定します。 そうでない場合は、opcdeftypeにデータ型の oid を 挿入するのではなくゼロを挿入してください。

INSERT INTO pg_opclass (opcname, opcdeftype) VALUES ('complex_abs_ops', 0);
   

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

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);
         }
   

Postgres に関数を登録するために下記のようにします。

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

ここで重要なことがいくつかあります。

まず、complex 用の「より小さい」、 「以下」、 「等しい」、「以上」、 「より大きい」の ための演算子が定義されようとしているということです。例えば、= という 演算子は一つしかもつことが出来ず、両方のオペランドを complex 型とします。この場合complex に対してほかの演算子 = はありません。しかしもし実用的なデータ型を 構築するなら、多分 = は複素数の通常の等しいという演算 になってほしいかと思います。その場合は、complex_abs_eq に対し なにか他の演算子名を使う必要があります。

二番目に、Postgres の場合は、違った入力データ型であれば同じ名前の 演算子を使うことができますが、C ではネームスペース内で一つのグローバル ルーチンが使えるだけです。ですから C 関数は abs_eq のような単純な名前にするべきではありません。 通常は C 関数名にデータ型名を入れておけば、他のデータ型の関数 と衝突することもありません。

三番目に、関数abs_eqの Postgres 名は、 Postgres が入力データ型によって同じ名前を持つ他の Postgres 関数 から区別してくれることを期待して作ることができます。 ここでは例を簡単にするために、関数に C レベルと Postgres レベル で同じ名前を与えます。

最後に、これらの演算子関数はブーリアンの値を返すことに注意して 下さい。アクセスメソッドはこの事実に頼っています。(一方で、サポート 関数は、特定のアクセスメソッドが期待するものを何でも返します。 この場合では符号付き整数です。)ファイルの最後のルーチンは、 pg_amテーブルの amsupport 列について説明 したときに出てきた「サポートルーチン」です。これは後に使います。 今のところは無視します。

これで演算子を定義する準備ができました。

CREATE OPERATOR = (
     leftarg = complex, rightarg = complex,
     procedure = complex_abs_eq,
     restrict = eqsel, join = eqjoinsel
         )
   
ここで重要なのは、プロシージャ名(つまり上記で定義される C 関数)と制約と結合選択関数です。 例で使われている選択関数をただ使って下さい complex.sourceを参照)。これらは「より小さい」 「等しい」そして「以上」の場合に使われる関数とは違うことに 注意して下さい。これらが供給されなければ、オプティマイザは インデックスを効果的に利用できません。

次のステップはこれらの演算子の項目をpg_amop リレーションに追加することです。そうするためには、上記で 定義したばかりの演算子のoidが必要です。 ここでは二つの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';

 opoid  | oprname
--------+---------
 277963 | +
 277970 | <
 277971 | <=
 277972 | =
 277973 | >=
 277974 | >
(6 rows)
   
(ここでまた、oid番号はほぼ確実に 違ったものになるでしょう。)ここで重要な演算子は 277970 から 277974 までのoidをもつものです。 得られる値はシステムによって異なるでしょうから、下記の値に 置き換えて下さい。ここでは SELECT 文で行います。

ここで、新しい演算子クラスでpg_amop を更新する準備ができました。この節全体で最も重要なことは、 pg_amopの中では演算子は「より小さい」 から「より大きい」への順番になっていることです。 必要な行を追加します。

    INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy)
        SELECT am.oid, opcl.oid, c.opoid, 1
        FROM pg_am am, pg_opclass opcl, complex_ops_tmp c
        WHERE amname = 'btree' AND
            opcname = 'complex_abs_ops' AND
            c.oprname = '<';
   
すぐに他の演算子についても、上記の三行目の "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
--------+-----------------
 277997 | complex_abs_cmp
(1 row)
   
(ここでまた、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';
   

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