ビューとルールシステム

Postgres におけるビューの実装

Views in Postgres are implemented using the rule system. In fact there is absolutely no difference between a

    CREATE VIEW myview AS SELECT * FROM mytab;
compared against the two commands
    CREATE TABLE myview (same attribute list as for mytab);
    CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
        SELECT * FROM mytab;
because this is exactly what the CREATE VIEW command does internally. This has some side effects. One of them is that the information about a view in the Postgres system catalogs is exactly the same as it is for a table. So for the query parsers, there is absolutely no difference between a table and a view. They are the same thing - relations. That is the important one for now.

Postgres におけるビューはルールシステム を利用して実装されています。実際において、

    CREATE VIEW myview AS SELECT * FROM mytab;
と、二つのコマンド
    CREATE TABLE myview (same attribute list as for mytab);
    CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
        SELECT * FROM mytab;
の間には全く差異がありません。 というのは、CREATE VIEW コマンドによって内部的に全く同じ 処理が行われるからです。しかし副作用もあります。 その一つは、Postgres システムカタログ のビューについての情報はテーブルの情報と同一ですので、パーサ にとってもテーブルとビューは同じものになります。リレーションに ついても同じです。現時点の重要課題です。

SELECT ルールの動き

Rules ON SELECT are applied to all queries as the last step, even if the command given is an INSERT, UPDATE or DELETE. And they have different semantics from the others in that they modify the parsetree in place instead of creating a new one. So SELECT rules are described first.

たとえコマンドが INSERT、 UPDATE または DELETE であっても、 ON SELECT ルールはすべての問合せに対し最後に適用され ます。 そして、それぞれは他と異なるせマンテックを持っていて パースツリーを新規に生成せずに、そこにあるものを修正します。 したがってSELECT ルールが一番初めに評価されなければなりません。

Currently, there could be only one action and it must be a SELECT action that is INSTEAD. This restriction was required to make rules safe enough to open them for ordinary users and it restricts rules ON SELECT to real view rules.

現時点では、たった一つのアクションだけが許されていて、 それは INSTEAD である SELECT アクションです。 この制約は一般のユーザが何をしてもルールシステムが堅牢で ある必要性があって、実のビューのルールに対する ON SELECT のルールを規制します。

The example for this document are two join views that do some calculations and some more views using them in turn. One of the two first views is customized later by adding rules for INSERT, UPDATE and DELETE operations so that the final result will be a view that behaves like a real table with some magic functionality. It is not such a simple example to start from and this makes things harder to get into. But it's better to have one example that covers all the points discussed step by step rather than having many different ones that might mix up in mind.

このドキュメントの例としてあげているのは、ちょっとした演算をする 二つのジョインのビューと、翻ってこれらの機能を利用するいくつかの ビューを取り上げます。 最終結果がなんらかのマジック機能によりあたかも実テーブルのよう に振舞うビューになるように、始めの二つのビューの内の一つが INSERT、UPDATE および DELETE 操作に対するルールを後で追加する ことでカスタマイズされます。 始めて学ぶための例としては決して簡単ではなく先に進むことを躊躇 させるかもしれませんが、多くの別々の例を持ち出して頭の混乱を招 くよりも、すべての論点をステップ毎に追ってゆく一つの例をあげる ほうが良いと思われます。

The database needed to play on the examples is named al_bundy. You'll see soon why this is the database name. And it needs the procedural language PL/pgSQL installed, because we need a little min() function returning the lower of 2 integer values. We create that as

    CREATE FUNCTION min(integer, integer) RETURNS integer AS
        'BEGIN
            IF $1 < $2 THEN
                RETURN $1;
            END IF;
            RETURN $2;
        END;'
    LANGUAGE 'plpgsql';

例として操作するデータベースの名前は al_bundy です。 なぜこのような名前なのかはすぐ解ります。 下位の二つの整数値を返すちょっとした min() 関数を必要としますので 手続き言語 PL/pgSQL がインストールされている必要があります。 関数の生成は以下のようにします。

    CREATE FUNCTION min(integer, integer) RETURNS integer AS
        'BEGIN
            IF $1 < $2 THEN
                RETURN $1;
            END IF;
            RETURN $2;
        END;'
    LANGUAGE 'plpgsql'; 

The real tables we need in the first two rule system descripitons are these:

    CREATE TABLE shoe_data (
        shoename   char(10),      -- primary key
        sh_avail   integer,       -- available # of pairs
        slcolor    char(10),      -- preferred shoelace color
        slminlen   float,         -- miminum shoelace length
        slmaxlen   float,         -- maximum shoelace length
        slunit     char(8)        -- length unit
    );

    CREATE TABLE shoelace_data (
        sl_name    char(10),      -- primary key
        sl_avail   integer,       -- available # of pairs
        sl_color   char(10),      -- shoelace color
        sl_len     float,         -- shoelace length
        sl_unit    char(8)        -- length unit
    );

    CREATE TABLE unit (
        un_name    char(8),       -- the primary key
        un_fact    float          -- factor to transform to cm
    );
I think most of us wear shoes and can realize that this is really useful data. Well there are shoes out in the world that don't require shoelaces, but this doesn't make Al's life easier and so we ignore it.

初めの二つのルールシステムの評価に必要な実際のテーブルは 以下のものです。

    CREATE TABLE shoe_data (
        shoename   char(10),      -- 主キー
        sh_avail   integer,       -- 在庫組数
        slcolor    char(10),      -- 好まれる靴紐の色
        slminlen   float,         -- 最短の靴紐の長さ
        slmaxlen   float,         -- 最長の靴紐の長さ
        slunit     char(8)        -- 長さの単位
    );

    CREATE TABLE shoelace_data (
        sl_name    char(10),      -- 主キー
        sl_avail   integer,       -- 在庫組数
        sl_color   char(10),      -- 靴紐の色
        sl_len     float,         -- 靴紐の長さ
        sl_unit    char(8)        -- 長さの単位
    );

    CREATE TABLE unit (
        un_name    char(8),       -- 主キー
        un_fact    float          -- cm 変換の要素
    );
読者のほとんどが靴を履くでしょうからこのデータは確かに 使えますよね。もちろん世の中には靴紐のいらない靴 もありますが、それではアルの生活が成り立ちませんので 無視することにします。

The views are created as

    CREATE VIEW shoe AS
        SELECT sh.shoename,
               sh.sh_avail,
               sh.slcolor,
               sh.slminlen,
               sh.slminlen * un.un_fact AS slminlen_cm,
               sh.slmaxlen,
               sh.slmaxlen * un.un_fact AS slmaxlen_cm,
               sh.slunit
          FROM shoe_data sh, unit un
         WHERE sh.slunit = un.un_name;

    CREATE VIEW shoelace AS
        SELECT s.sl_name,
               s.sl_avail,
               s.sl_color,
               s.sl_len,
               s.sl_unit,
               s.sl_len * u.un_fact AS sl_len_cm
          FROM shoelace_data s, unit u
         WHERE s.sl_unit = u.un_name;

    CREATE VIEW shoe_ready AS
        SELECT rsh.shoename,
               rsh.sh_avail,
               rsl.sl_name,
               rsl.sl_avail,
               min(rsh.sh_avail, rsl.sl_avail) AS total_avail
          FROM shoe rsh, shoelace rsl
         WHERE rsl.sl_color = rsh.slcolor
           AND rsl.sl_len_cm >= rsh.slminlen_cm
           AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
The CREATE VIEW command for the shoelace view (which is the simplest one we have) will create a relation shoelace and an entry in pg_rewrite that tells that there is a rewrite rule that must be applied whenever the relation shoelace is referenced in a queries rangetable. The rule has no rule qualification (discussed in the non SELECT rules since SELECT rules currently cannot have them) and it is INSTEAD. Note that rule qualifications are not the same as query qualifications! The rules action has a qualification.

ビューは次のようにして作られます。

    CREATE VIEW shoe AS
        SELECT sh.shoename,
               sh.sh_avail,
               sh.slcolor,
               sh.slminlen,
               sh.slminlen * un.un_fact AS slminlen_cm,
               sh.slmaxlen,
               sh.slmaxlen * un.un_fact AS slmaxlen_cm,
               sh.slunit
          FROM shoe_data sh, unit un
         WHERE sh.slunit = un.un_name;

    CREATE VIEW shoelace AS
        SELECT s.sl_name,
               s.sl_avail,
               s.sl_color,
               s.sl_len,
               s.sl_unit,
               s.sl_len * u.un_fact AS sl_len_cm
          FROM shoelace_data s, unit u
         WHERE s.sl_unit = u.un_name;

    CREATE VIEW shoe_ready AS
        SELECT rsh.shoename,
               rsh.sh_avail,
               rsl.sl_name,
               rsl.sl_avail,
               min(rsh.sh_avail, rsl.sl_avail) AS total_avail
          FROM shoe rsh, shoelace rsl
         WHERE rsl.sl_color = rsh.slcolor
           AND rsl.sl_len_cm >= rsh.slminlen_cm
           AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
(今ある一番簡単な) shoelace ビュー用の CREATE VIEW コマンドは、リレーション shoelace と問合せ 範囲テーブルの中でリレーション shoelace が参照される時はいつでも、 適用されるべき書き換えルールの存在を示す項目を pg_rewrite に作ります。 ルールはルール条件を持たない INSTEAD です。 (SELECT rule は現在規定されていないので、非 SELECT ルール のところで取り上げられます。) ルールの条件は、問合せの条件とは異なることに注意して下さい! ルールアクションは条件を持っています。

The rules action is one querytree that is an exact copy of the SELECT statement in the view creation command.

ルールアクションはビュー生成コマンドの SELECT 文とまったく同じ 問合せツリーです。

注意書き: The two extra range table entries for NEW and OLD (named *NEW* and *CURRENT* for historical reasons in the printed querytree) you can see in the pg_rewrite entry aren't of interest for SELECT rules.

pg_rewrite 項目の (歴史的な理由により、出力用の問合せツリーではされは *NEW* および *CURRENT* という名前がついている) NEW および OLD に   たいする二つの特別な範囲テーブル項目は SELECT ルールには関係   ないことが解ります。

Now we populate unit, shoe_data and shoelace_data and Al types the first SELECT in his life: ではここで unitshoe_data および shoelace_data にデータをいれます。 アルはここで初めての SELECT を入力します。
    al_bundy=> INSERT INTO unit VALUES ('cm', 1.0);
    al_bundy=> INSERT INTO unit VALUES ('m', 100.0);
    al_bundy=> INSERT INTO unit VALUES ('inch', 2.54);
    al_bundy=> 
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh1', 2, 'black', 70.0, 90.0, 'cm');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh2', 0, 'black', 30.0, 40.0, 'inch');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
    al_bundy=> 
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl1', 5, 'black', 80.0, 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl2', 6, 'black', 100.0, 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl3', 0, 'black', 35.0 , 'inch');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl4', 8, 'black', 40.0 , 'inch');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl5', 4, 'brown', 1.0 , 'm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl6', 0, 'brown', 0.9 , 'm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl7', 7, 'brown', 60 , 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl8', 1, 'brown', 40 , 'inch');
    al_bundy=> 
    al_bundy=> SELECT * FROM shoelace;
    sl_name   |sl_avail|sl_color  |sl_len|sl_unit |sl_len_cm
    ----------+--------+----------+------+--------+---------
    sl1       |       5|black     |    80|cm      |       80
    sl2       |       6|black     |   100|cm      |      100
    sl7       |       7|brown     |    60|cm      |       60
    sl3       |       0|black     |    35|inch    |     88.9
    sl4       |       8|black     |    40|inch    |    101.6
    sl8       |       1|brown     |    40|inch    |    101.6
    sl5       |       4|brown     |     1|m       |      100
    sl6       |       0|brown     |   0.9|m       |       90
    (8 rows)
It's the simplest SELECT Al can do on our views, so we take this to explain the basics of view rules. The 'SELECT * FROM shoelace' was interpreted by the parser and produced the parsetree アルが行うビューにたいする最も簡単な SELECT ですので、これで ビューの基本ルールを説明します。 'SELECT * FROM shoelace' はパーサによって処理され、次の パースツリーが生成されます。
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace;
and this is given to the rule system. The rule system walks through the rangetable and checks if there are rules in pg_rewrite for any relation. When processing the rangetable entry for shoelace (the only one up to now) it finds the rule '_RETshoelace' with the parsetree この操作はルールシステムに伝えられます。ルールシステムは 範囲テーブルを参照し、なんらかのリレーションにたいしてルールが pg_rewrite に存在するか調べます。 (現時点ではたった一つの) shoelace にたいする 範囲テーブル項目を処理するときにパースツリーでルール '_RETshoelace' を見つけ出します。
    SELECT s.sl_name, s.sl_avail,
           s.sl_color, s.sl_len, s.sl_unit,
           float8mul(s.sl_len, u.un_fact) AS sl_len_cm
      FROM shoelace *OLD*, shoelace *NEW*,
           shoelace_data s, unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
Note that the parser changed the calculation and qualification into calls to the appropriate functions. But in fact this changes nothing. The first step in rewriting is merging the two rangetables. The resulting parsetree then reads パーサが、演算と条件を適切な関数の呼び出しに変換した事に 注意して下さい。 しかし実際は、これは何も変更しません。 書き換えの最初のステップは二つの範囲テーブルをマージすることです。 これによってパースツリーは次のようになります。
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u;
In step 2 it adds the qualification from the rule action to the parsetree resulting in ステップ 2 で、ルールアクションから条件をパースツリーに 付け加え、以下の結果をもたらします。
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
And in step 3 it replaces all the variables in the parsetree, that reference the rangetable entry (the one for shoelace that is currently processed) by the corresponding targetlist expressions from the rule action. This results in the final query 次に、ステップ 3 では範囲テーブルの項目 (現在処理されている shoelace に対するものの一つ)を参照する パースツリーのすべての変数をルールアクションからの対応する目的 リストの式で置換します。そして、最終の問合せとなります。
    SELECT s.sl_name, s.sl_avail, 
           s.sl_color, s.sl_len, 
           s.sl_unit, float8mul(s.sl_len, u.un_fact) AS sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
Turning this back into a real SQL statement a human user would type reads 翻って、人間の手で入力する場合の実際の SQL 文 は次の通りです。
    SELECT s.sl_name, s.sl_avail,
           s.sl_color, s.sl_len,
           s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm
      FROM shoelace_data s, unit u
     WHERE s.sl_unit = u.un_name;
That was the first rule applied. While this was done, the rangetable has grown. So the rule system continues checking the range table entries. The next one is number 2 (shoelace *OLD*). Relation shoelace has a rule, but this rangetable entry isn't referenced in any of the variables of the parsetree, so it is ignored. Since all the remaining rangetable entries either have no rules in pg_rewrite or aren't referenced, it reaches the end of the rangetable. Rewriting is complete and the above is the final result given into the optimizer. The optimizer ignores the extra rangetable entries that aren't referenced by variables in the parsetree and the plan produced by the planner/optimizer would be exactly the same as if Al had typed the above SELECT query instead of the view selection. これは適用される最初のルールです。この間に範囲テーブルは成長 します。ですから、ルールシステムは範囲テーブルの項目をチェック し続けます。次は第二番 (shoelace *OLD*) です。 リレーション shoelace はルールを持っていますが この範囲テーブル項目はパースツリーのいかなる変数によっても参照 されないので無視されます。 残りの全ての範囲テーブル項目はpg_rewrite にルールが無いか参照されないかのいずれかですので、範囲テーブルの 終りに行きつきます。 書き換えは完了し、上記がオプティマイザに渡される最終結果となります。 オプティマイザは、パースツリーの変数で参照されない特別の 範囲テーブル項目を無視し、ビューの選択によってではなく、アルが入力 した上記の SELECT と全く同じのプランがプランナ/オプティマイザ によって生成されます。

Now we face Al with the problem that the Blues Brothers appear in his shop and want to buy some new shoes, and as the Blues Brothers are, they want to wear the same shoes. And they want to wear them immediately, so they need shoelaces too.

ここで、ブルース兄弟が靴を買いにお店にやって来て、兄弟ですから、 同じ靴を履きたい、当然靴紐も同じものを、と言って来たときアルは 問題を抱えることになります。

Al needs to know for which shoes currently in the store he has the matching shoelaces (color and size) and where the total number of exactly matching pairs is greater or equal to two. We theach him how to do and he asks his database:

店に置いてある靴紐の(色とサイズ)に一致する靴がどれで、 完全に一致するものの在庫が 2 組以上あるかどうかをアルは 知る必要に迫られます。 私達はアルにデータベースに問い合わせることを教えます。

    al_bundy=> SELECT * FROM shoe_ready WHERE total_avail >= 2;
    shoename  |sh_avail|sl_name   |sl_avail|total_avail
    ----------+--------+----------+--------+-----------
    sh1       |       2|sl1       |       5|          2
    sh3       |       4|sl7       |       7|          4
    (2 rows)
Al is a shoe guru and so he knows that only shoes of type sh1 would fit (shoelace sl7 is brown and shoes that need brown shoelaces aren't shoes the Blues Brothers would ever wear). 靴についてアルは専門家ですので、sh1 型の靴が該当することが 判ります。(sl7 の靴紐は茶色で、それに合う茶色の靴はブルース 兄弟の好みではありません。)

The output of the parser this time is the parsetree

今回のパーサの出力は以下のパースツリーです。

    SELECT shoe_ready.shoename, shoe_ready.sh_avail,
           shoe_ready.sl_name, shoe_ready.sl_avail,
           shoe_ready.total_avail
      FROM shoe_ready shoe_ready
     WHERE int4ge(shoe_ready.total_avail, 2);
The first rule applied will be that one for the shoe_ready relation and it results in the parsetree 一番始めに適用されるルールは shoe_ready リレーション用のものでパースツリーにおける結果は以下のように なります。
    SELECT rsh.shoename, rsh.sh_avail,
           rsl.sl_name, rsl.sl_avail,
           min(rsh.sh_avail, rsl.sl_avail) AS total_avail
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, shoe rsh,
           shoelace rsl
     WHERE int4ge(min(rsh.sh_avail, rsl.sl_avail), 2)
       AND (bpchareq(rsl.sl_color, rsh.slcolor)
            AND float8ge(rsl.sl_len_cm, rsh.slminlen_cm)
            AND float8le(rsl.sl_len_cm, rsh.slmaxlen_cm)
           );
In reality the AND clauses in the qualification will be operator nodes of type AND with a left and right expression. But that makes it lesser readable as it already is, and there are more rules to apply. So I only put them into some parantheses to group them into logical units in the order they where added and we continue with the rule for relation shoe as it is the next rangetable entry that is referenced and has a rule. The result of applying it is 実際、条件の中の AND 句は左右に式を有する AND 型の演算子ノードです。 しかし、前とは違ってより解りにくい結果となり、適用するルールもより 多くなります。 ですから、筆者はこれらを論理単位にグループ化するために付け加え られた場所の順序にしたがって括弧で括り、参照される次の範囲テーブル 項目であってルールを持っている リレーション shoe に対するルールに進むことにします。 適用の結果は以下の通りです。
    SELECT sh.shoename, sh.sh_avail,
           rsl.sl_name, rsl.sl_avail,
           min(sh.sh_avail, rsl.sl_avail) AS total_avail,
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, shoe rsh,
           shoelace rsl, shoe *OLD*,
           shoe *NEW*, shoe_data sh,
           unit un
     WHERE (int4ge(min(sh.sh_avail, rsl.sl_avail), 2)
            AND (bpchareq(rsl.sl_color, sh.slcolor)
                 AND float8ge(rsl.sl_len_cm, 
                              float8mul(sh.slminlen, un.un_fact))
                 AND float8le(rsl.sl_len_cm, 
                              float8mul(sh.slmaxlen, un.un_fact))
                )
           )
       AND bpchareq(sh.slunit, un.un_name);
And finally we apply the already well known rule for shoelace (this time on a parsetree that is a little more complex) and get 最終的に既に良く知られている shoelace に対する ルールを適用し(ここでは、多少複雑になったパースツリー上のもの)そして 結果を得ます。
    SELECT sh.shoename, sh.sh_avail,
           s.sl_name, s.sl_avail,
           min(sh.sh_avail, s.sl_avail) AS total_avail
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, shoe rsh,
           shoelace rsl, shoe *OLD*,
           shoe *NEW*, shoe_data sh,
           unit un, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u
     WHERE (    (int4ge(min(sh.sh_avail, s.sl_avail), 2)
                 AND (bpchareq(s.sl_color, sh.slcolor)
                      AND float8ge(float8mul(s.sl_len, u.un_fact), 
                                   float8mul(sh.slminlen, un.un_fact))
                      AND float8le(float8mul(s.sl_len, u.un_fact), 
                                   float8mul(sh.slmaxlen, un.un_fact))
                     )
                )
            AND bpchareq(sh.slunit, un.un_name)
           )
       AND bpchareq(s.sl_unit, u.un_name);
Again we reduce it to a real SQL statement that is equivalent to the final output of the rule system: もう一度、これをルールシステムの最終結果と同一の結果をもたらす 実際の単純な形の SQL 文にしてみます。
    SELECT sh.shoename, sh.sh_avail,
           s.sl_name, s.sl_avail,
           min(sh.sh_avail, s.sl_avail) AS total_avail
      FROM shoe_data sh, shoelace_data s, unit u, unit un
     WHERE min(sh.sh_avail, s.sl_avail) >= 2
       AND s.sl_color = sh.slcolor
       AND s.sl_len * u.un_fact >= sh.slminlen * un.un_fact
       AND s.sl_len * u.un_fact <= sh.slmaxlen * un.un_fact
       AND sh.sl_unit = un.un_name
       AND s.sl_unit = u.un_name;
Recursive processing of rules rewrote one SELECT from a view into a parsetree, that is equivalent to exactly that what Al had to type if there would be no views at all. ルールの再帰的処理はビューからの一つの SELECT をパースツリー に書き換えます。これはビューが全く無かったときアルが入力 したものと全く同等です。

注意書き: There is currently no recursion stopping mechanism for view rules in the rule system (only for the other rules). This doesn't hurt much, because the only way to push this into an endless loop (blowing up the backend until it reaches the memory limit) is to create tables and then setup the view rules by hand with CREATE RULE in such a way, that one selects from the other that selects from the one. This could never happen if CREATE VIEW is used because on the first CREATE VIEW, the second relation does not exist and thus the first view cannot select from the second.

ルールシステムにおいて(他のルールは別ですが)ビュールールの 再帰処理を中止する機構はありません。 この無限ループ(メモリの限界に到達してバックエンドが破壊される) を引き起こすにはには、テーブルを作成しCREATE RULE により 手作業でビュールールを他からの選択が、その他からのを選択する ように設定することによりのみ可能ですから、さほど深刻な問題 ではありません。 初めの CREATE VIEW では、二番目のリレーションは存在せず 第一のビューを第二のビューが選択することは有りえませんから このような状況は CREATE VIEW で起こり得ません。

非 SELECT 文のビュールール

Two details of the parsetree aren't touched in the description of view rules above. These are the commandtype and the resultrelation. In fact, view rules don't need these informations.

これまでのビュールールの説明ではパースツリーの詳細二点 について触れませんでした。それらは、コマンドタイプおよび 結果リレーションです。実際、ビューのルールはこれらの情報を 必要としません。

There are only a few differences between a parsetree for a SELECT and one for any other command. Obviously they have another commandtype and this time the resultrelation points to the rangetable entry where the result should go. Anything else is absolutely the same. So having two tables t1 and t2 with attributes a and b, the parsetrees for the two statements

SELECT と他のコマンドの対するパースツリーの間には大きな差異が有りません。 明らかにそれらは別のコマンドタイプを持っていて、今回は結果リレーション は結果がどこに行くのかを示す範囲テーブルの項目を指し示します。 このこと以外は全く同一です。 ですから、a と b の属性を持つテーブル t1 および t2 に対しての 二つの命令文のパースツリー、

    SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;

    UPDATE t1 SET b = t2.b WHERE t1.a = t2.a;
are nearly identical. はほとんど同等です。

The consequence is, that both parsetrees result in similar execution plans. They are both joins over the two tables. For the UPDATE the missing columns from t1 are added to the targetlist by the optimizer and the final parsetree will read as 結果、両方のパースツリーが似たような実行プランになります。 それらは共に二つのテーブルにまたがったジョインです。UPDATE に対してt1 から抜けているカラムはオプティマイザが目的リスト に追加し、最終のパースツリーは、
    UPDATE t1 SET a = t1.a, b = t2.b WHERE t1.a = t2.a;
and thus the executor run over the join will produce exactly the same result set as a のようになって、ジョインを実行したエクゼキュータは、
    SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
will do. But there is a little problem in UPDATE. The executor does not care what the results from the join it is doing are meant for. It just produces a result set of rows. The difference that one is a SELECT command and the other is an UPDATE is handled in the caller of the executor. The caller still knows (looking at the parsetree) that this is an UPDATE, and he knows that this result should go into table t1. But which of the 666 rows that are there has to be replaced by the new row? The plan executed is a join with a qualification that potentially could produce any number of rows between 0 and 666 in unknown order. と全く同じ結果のセットを作成します。とは言っても、UPDATE には ちょっとした問題があります。エクゼキュータは、ジョインが行う処理 の結果が何を意味しているかに関与しません。 エクゼキュータは単に結果となる行のセットを作成するだけです。 一つは SELECT コマンド でもう一つは UPDATE コマンドですが、相違はエクゼキュータを呼び出 す側で扱われることです。呼び出し側は( パースツリーを見て)、これが UPDATE であると解っていて、この結果がテーブル t1 に入らなければ ならないことを知っています。しかし、666 ある行の内、どの行が新し い行によって置換されなければならないのでしょうか。 実行されたプランは条件による 0 から 666 の間の 順序 の決まらないどんな数の行でも潜在的に生成し得るジョインです。

To resolve this problem, another entry is added to the targetlist in UPDATE and DELETE statements. The current tuple ID (ctid). This is a system attribute with a special feature. It contains the block and position in the block for the row. Knowing the table, the ctid can be used to find one specific row in a 1.5GB sized table containing millions of rows by fetching one single data block. After adding the ctid to the targetlist, the final result set could be defined as

この問題を解決するため、UPDATE と DELETE 文の目的リストに別の 項目が付け加えられます。それは現在のタプル ID (ctid) です。 これは特別の機能をもったシステムの属性です。それは、その行に対 する当該ブロックの中のブロックと位置を持っています。テーブルが判っ ている場合、一つのデータブロックをフェッチすることによって数百万 の行を含む 1.5GB 容量のテーブルの中の一つの特定の行を 見つけ出すために ctid が利用出来ます。目的リストに ctid を付け加えた後、最終の結果のセットは以下のように定義されます。

    SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
Now another detail of Postgres enters the stage. At this moment, table rows aren't overwritten and this is why ABORT TRANSACTION is fast. In an UPDATE, the new result row is inserted into the table (after stripping ctid) and in the tuple header of the row that ctid pointed to the cmax and xmax entries are set to the current command counter and current transaction ID. Thus the old row is hidden and after the transaction commited the vacuum cleaner can really move it out. では Postgres の別の詳細説明に入ります。 この時点で、テーブルの行は上書きされていませんので ABORT TRANSACTION 処理は速いのです。UPDATE では(ctid を取り除いたあと)テーブルに 新結果の行が挿入され、その行のタプルヘッダ内では cmax を指し示す ctid と xmax の項目が現在のコマンドカウンタと現在のトランザクション ID にセットされます。 このようにして、旧行は隠されトランザクションがコミットされた時点 で掃除機が実際に削除します。

Knowing that all, we can simply apply view rules in absolutely the same way to any command. There is no difference.

これらの詳細が全部解れば、どんなコマンドにたいしても全く同じように してビューのルールを単純に適用することが出来ます。そこに差異は存在 しません。

Postgres における View の能力

The above demonstrates how the rule system incorporates view definitions into the original parsetree. In the second example a simple SELECT from one view created a final parsetree that is a join of 4 tables (unit is used twice with different names).

ここまでで、ビューの諸定義がオリジナルのパースツリーにどのように関与 するかを解説しました。第二の例では、簡単な SELECT from one view で四つのテーブルのジョインである最終パースツリーが作られます。 (事例は違った名前で二度使われます。)

利点

The benefit of implementing views with the rule system is, that the optimizer has all the information about which tables have to be scanned plus the relationships between these tables plus the restrictive qualifications from the views plus the qualifications from the original query in one single parsetree. And this is still the situation when the original query is already a join over views. Now the optimizer has to decide which is the best path to execute the query. The more information the optimizer has, the better this decision can be. And the rule system as implemented in Postgres ensures, that this is all information available about the query up to now.

ビューをルールシステムとともに実装する利点は、 オプティマイザが、どのテーブルをスキャンすべきか、および またそれらのテーブル間の関連性、および ビューからの制約条件、およびたった一つのパースツリー内にある オリジナルの問合わせ条件についての情報を持っていることです。 オリジナルの問合せが既にビューに対するジョインである時の状況 でもあります。 オプティマイザはここでどれが問合せ処理の最適経路かを決定 しなければなりません。情報が多ければ多いほど、オプティマイザ はより良い決定を下すことが出来ます。そして Postgres に実装されているルール システムではこれが現時点で、問合せにたいするすべての利用可能 な情報です。

関心事項

There was a long time where the Postgres rule system was considered broken. The use of rules was not recommended and the only part working where view rules. And also these view rules made problems because the rule system wasn't able to apply them properly on other statements than a SELECT (for example an UPDATE that used data from a view didn't work).

Postgres のルールシステムは動かないと 考えられた長い期間がありました。ルールの使用は推奨されず、ビューの ルールは一部のみしか機能しませんでした。 SELECT 以外の他の文にルール システムが正しく適用されないため、これらのビューのルールは問題 (例えばビューのデータを使用した UPDATE が機能しない)を引き起こしま した。

During that time, development moved on and many features where added to the parser and optimizer. The rule system got more and more out of sync with their capabilities and it became harder and harder to start fixing it. Thus, noone did.

この期間、開発は継続され数多くの機能がパーサとオプティマイザに 追加されました。ルールシステムは他の機能と同期をとることからどん どん外れて行き、そこからの修復がますます難しくなりました。そして だれもやらなくなりました。

For 6.4, someone locked the door, took a deep breath and shuffled that damned thing up. What came out was a rule system with the capabilities described in this document. But there are still some constructs not handled and some where it fails due to things that are currently not supported by the Postgres query optimizer.

6.4 のため、だれかが部屋に閉じこもり、深呼吸をして、そしてこの どうしようもないものをいじくり廻しました。そして出来上がったも のがこのドキュメントで解説した機能をもつルールシステムでした。 とはいっても、まだ扱われていない機能や現時点では Postgres の問合せオプティマイザに よってサポートされていない事によるいくつかの不都合もあります。

  • Views with aggregate columns have bad problems. Aggregate expressions in qualifications must be used in subselects. Currently it is not possible to do a join of two views, each having an aggregate column, and compare the two aggregate values in the qualification. In the meantime it is possible to put these aggregate expressions into functions with the appropriate arguments and use them in the view definition.

    集約したカラムをもっているビューは悪質な問題を抱えています。 条件の中の集約式は subselects で用いられなければ成りません。 現時点で、それぞれが集約カラムを持っていて、条件の中で 二つの集約値を比較する二つのビューのジョインを行うことは 不可能です。とりあえず、それらの集約式を適切な引数を持った 関数にしてビューの定義の中で使用すればこのことは可能です。

  • Views of unions are currently not supported. Well it's easy to rewrite a simple SELECT into a union. But it is a little difficult if the view is part of a join doing an update.

    union のビューは今のところサポートされていません。簡単な SELECT を union に書き換えるのは簡単ですが、もしビューが更新作業 を行っているジョインの一部である場合多少難しくなります。

  • ORDER BY clauses in view definitions aren't supported.

    ビュー定義において ORDER BY 句はサポートされていません。

  • DISTINCT isn't supported in view definitions.

    DISTINCT はビュー定義でサポートされていません。

There is no good reason why the optimizer should not handle parsetree constructs that the parser could never produce due to limitations in the SQL syntax. The author hopes that these items disappear in the future. SQL 構文中での制約でパーサが絶対に生成 することが出来ないパースツリー構成をオプティマイザが操作して はならないとする適切な理由はありません。著者は将来この項目 が無くなることを望みます。

実装による副作用

Using the described rule system to implement views has a funny side effect. The following does not seem to work:

ビューを実装するために、説明したルールシステムを使用すると 妙な副作用がでます。

    al_bundy=> INSERT INTO shoe (shoename, sh_avail, slcolor)
    al_bundy->     VALUES ('sh5', 0, 'black');
    INSERT 20128 1
    al_bundy=> SELECT shoename, sh_avail, slcolor FROM shoe_data;
    shoename  |sh_avail|slcolor   
    ----------+--------+----------
    sh1       |       2|black     
    sh3       |       4|brown     
    sh2       |       0|black     
    sh4       |       3|brown     
    (4 rows)
The interesting thing is that the return code for INSERT gave us an object ID and told that 1 row has been inserted. But it doesn't appear in shoe_data. Looking into the database directory we can see, that the database file for the view relation shoe seems now to have a data block. And that is definitely the case. 興味深いこととして INSERT の返りコードはオブジェクト ID を 引き渡してくれて、一つの行が挿入されたことを示しますが、 その行は shoe_data には現れません。 データベースディレクトリを見ると、ビューのリレーション shoe に対するデータベースファイルは ここにおいて、データブロックを持っているようです。 そして、まさにこれがその現象です。

We can also issue a DELETE and if it does not have a qualification, it tells us that rows have been deleted and the next vacuum run will reset the file to zero size.

同様にして DELETE も発行することが出来、もし条件が付いて いなければ、行が既に削除されていて次回の vacuum でファイル サイズがゼロにリセットされます。

The reason for that behaviour is, that the parsetree for the INSERT does not reference the shoe relation in any variable. The targetlist contains only constant values. So there is no rule to apply and it goes down unchanged into execution and the row is inserted. And so for the DELETE.

なぜこのように振る舞うのかと言うと、INSERT に対するパースツリー は shoe リレーションのどんな変数も参照 しないからです。目的リストには定数値しかありません。 ですから適用するルールが無く、変更なしで実行される結果、その 行が挿入されます。DELETE の場合も同じです。

To change this we can define rules that modify the behaviour of non-SELECT queries. This is the topic of the next section.

この処理を変更するには、非 SELECT の問合せの振舞いを修正する ルールを定義します。次の節の論題です。