INSERT、 UPDATE および DELETE についてのルール

ビュールールに対しての差異

Rules that are defined ON INSERT, UPDATE and DELETE are totally different from the view rules described in the previous section. First, their CREATE RULE command allows more:

ON INSERT、UPDATE および DELETE でのルールは前節で解説 したビューのルールとは全く異なります。第一点として、これらの CREATE RULE コマンドは数多くの設定を取り仕切っています。

Second, they don't modify the parsetree in place. Instead they create zero or many new parsetrees and can throw away the original one. 第二点として、その場所でパースツリーを修正しません。そのかわりに 新規の複数(ゼロの場合もありますが)のパースツリーを生成して、 オリジナルを破棄します。

これらのルールの動作k

Keep the syntax

    CREATE RULE rule_name AS ON event
        TO object [WHERE rule_qualification]
        DO [INSTEAD] [action | (actions) | NOTHING];
in mind. In the following, "update rules" means rules that are defined ON INSERT, UPDATE or DELETE. 上記構文を覚えておいて下さい。以下では、 "ルールの更新" は 定義された ON INSERT、 UPDATE または DELETE ルールを意味します。

Update rules get applied by the rule system when the result relation and the commandtype of a parsetree are equal to the object and event given in the CREATE RULE command. For update rules, the rule system creates a list of parsetrees. Initially the parsetree list is empty. There can be zero (NOTHING keyword), one or multiple actions. To simplify, we look at a rule with one action. This rule can have a qualification or not and it can be INSTEAD or not.

ルールの更新はルールシステムによって、結果リレーションと パースツリーのコマンドタイプがオブジェクトと CREATE RULE で 与えられるイベントと等しい場合に適用されます。 ルールの更新に対してルールシステムはパースツリーのリストを生成 します。初めの段階でパースツリーリストは空です。 ゼロ(キーワードが NOTHING)か、一つかまたは複数のアクションが 有効です。簡単にするため、ここでは一つのアクションのルールを 取り上げます。このルールは条件を持っていても持っていなくても、 また INSTEAD であっても、無くても構いません。

What is a rule qualification? It is a restriction that tells when the actions of the rule should be done and when not. This qualification can only reference the NEW and/or OLD pseudo relations which are basically the relation given as object (but with a special meaning).

ルール条件とはどんなものでしょうか。それはルールのアクション を行われなければならない時と、行ってはならない時を指定する 条件です。基本的に(特別な意味合いをもった)オブジェクトとして 与えられるリレーションである NEW そして、あるいは OLD 疑似 リレーションのみを、この条件は参照します。

So we have four cases that produce the following parsetrees for a one-action rule.

一つのアクションルールに対し、以下のパースツリーを生成する四つの 事例があげられます。

Finally, if the rule is not INSTEAD, the unchanged original parsetree is added to the list. Since only qualified INSTEAD rules already add the original parsetree, we end up with a total maximum of two parsetrees for a rule with one action.

最終的に、ルールが INSTEAD でない場合、変更されていない オリジナルのパースツリーがリストに付け加えられます。 条件に合った INSTEAD ルールのみが既にオリジナルのパースツリーに追加を しているので、最後は一つのアクションに対して最大合わせて二つの パースツリーに行きつきます。

The parsetrees generated from rule actions are thrown into the rewrite system again and maybe more rules get applied resulting in more or less parsetrees. So the parsetrees in the rule actions must have either another commandtype or another resultrelation. Otherwise this recursive process will end up in a loop. There is a compiled in recursion limit of currently 10 iterations. If after 10 iterations there are still update rules to apply the rule system assumes a loop over multiple rule definitions and aborts the transaction.

ルールアクションで生成されたパースツリーは再度書き換えシステムに 手渡され、相当数のパースツリーの結果をもたらす、より多くのルールの 適用を受けることもあります。ですから、ルールアクションにおける パースツリーは別のコマンドタイプか、別の結果リレーションを持って いなければなりません。さもないとこの再帰的手順はループになって しまいます。現在 10 回までの再帰処理反復制限が組み込まれています。 10 回反復処理を行った後にもまだ適用すべき更新ルールがあった場合は、、 ルールシステムは複数のルール定義にまたがったループであると想定し、 そのトランザクションを停止します。

The parsetrees found in the actions of the pg_rewrite system catalog are only templates. Since they can reference the rangetable entries for NEW and OLD, some substitutions have to be made before they can be used. For any reference to NEW, the targetlist of the original query is searched for a corresponding entry. If found, that entries expression is placed into the reference. Otherwise NEW means the same as OLD. Any reference to OLD is replaced by a reference to the rangetable entry which is the resultrelation.

pg_rewrite システムカタログのアクションで 見受けられるパースツリーは単なるテンプレートです。これらは NEW と OLD に対する範囲テーブルの項目を参照することが出来るため 使用される前に何らかの置換措置がとられていなければなりません。 NEW に対するどんな参照でも、オリジナルの問合せの目的リストは 関連する項目があるかどうか検索されます。 項目が見つかった場合には、その項目式は参照の中に組み込まれます。 項目がなかった場合、NEW は OLD と同じ意味になります。 OLD に対するどんな参照でも結果リレーションである範囲テーブルの 項目に対する参照に取って代わられます。

最初のルール ステップ・バイ・ステップ

We want to trace changes to the sl_avail column in the shoelace_data relation. So we setup a log table and a rule that writes us entries every time and UPDATE is performed on shoelace_data.

shoelace_data リレーションの sl_avail カラムの変化を追跡してみたいと思います。 そこでログ用テーブルと毎回の入力および shoelace_data に対して行われる UPDATE を記録するルールを用意しました。

    CREATE TABLE shoelace_log (
        sl_name    char(10),      -- shoelace changed
        sl_avail   integer,       -- new available value
        log_who    name,          -- who did it
        log_when   datetime       -- when
    );

    CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data
        WHERE NEW.sl_avail != OLD.sl_avail
        DO INSERT INTO shoelace_log VALUES (
                                        NEW.sl_name,
                                        NEW.sl_avail,
                                        getpgusername(),
                                        'now'::text
                                    );
    CREATE TABLE shoelace_log (
        sl_name    char(10),      -- 変更された靴紐
        sl_avail   integer,       -- 新規の有効な値
        log_who    name,          -- 誰が変更したか
        log_when   datetime       -- いつ変更したか
    );

    CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data
        WHERE NEW.sl_avail != OLD.sl_avail
        DO INSERT INTO shoelace_log VALUES (
                                        NEW.sl_name,
                                        NEW.sl_avail,
                                        getpgusername(),
                                        'now'::text
                                    );
One interesting detail is the casting of 'now' in the rules INSERT action to type text. Without that, the parser would see at CREATE RULE time, that the target type in shoelace_log is a datetime and tries to make a constant from it - with success. So a constant datetime value would be stored in the rule action and all log entries would have the time of the CREATE RULE statement. Not exactly what we want. The casting causes that the parser constructs a datetime('now'::text) from it and this will be evaluated when the rule is executed. ひとつの興味ある項目として、INSERT アクションルールの中で、 'now' をテキスト型にキャストすることです。 そうしないとパーサは CREATE RULE の time を見に行き、 shoelace_log にあるターゲットの型は datetime 型と判断してそれから定数を生成しようとします。 そして生成に成功します。そして、datetime の定数値が アクションルールに記憶され、全てのログ項目はその CREATE RULE 文の time を持つことになります。これは望まれる事ではありません。 キャストによりパーサは datetime('now'::text) をそのまま構築する ようになり、ルールが実行される時にこれ(datetime('now'::text))が 評価されます。

Now Al does

そこでアルは、

    al_bundy=> UPDATE shoelace_data SET sl_avail = 6                       
    al_bundy->     WHERE sl_name = 'sl7';
and we look at the logtable. と入力しました。ログテーブルを見てみましょう。
    al_bundy=> SELECT * FROM shoelace_log;
    sl_name   |sl_avail|log_who|log_when                        
    ----------+--------+-------+--------------------------------
    sl7       |       6|Al     |Tue Oct 20 16:14:45 1998 MET DST
    (1 row)
That's what we expected. What happened in the background is the following. The parser created the parsetree (this time the parts of the original parsetree are highlighted because the base of operations is the rule action for update rules). 思った結果が出ました。裏ではパーサがパースツリーを生成しました。 (今回、操作の基本は更新ルールにたいするルールアクションのため、 基となったパースツリーの部分部分は強調されています。)
    UPDATE shoelace_data SET sl_avail = 6
      FROM shoelace_data shoelace_data
     WHERE bpchareq(shoelace_data.sl_name, 'sl7');
There is a rule 'log_shoelace' that is ON UPDATE with the rule qualification expression ルールの条件式を持つ ON UPDATE の 'log_shoelace' ルールと、
    int4ne(NEW.sl_avail, OLD.sl_avail)
and one action 一つのアクション
    INSERT INTO shoelace_log SELECT 
           *NEW*.sl_name, *NEW*.sl_avail,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data *NEW*, shoelace_data *OLD*,
           shoelace_log shoelace_log;
があります。 Don't trust the output of the pg_rules system view. It specially handles the situation that there are only references to NEW and OLD in the INSERT and outputs the VALUES format of INSERT. In fact there is no difference between an INSERT ... VALUES and an INSERT ... SELECT on parsetree level. They both have rangetables, targetlists and maybe qualifications etc. The optimizer later decides, if to create an execution plan of type result, seqscan, indexscan, join or whatever for that parsetree. If there are no references to rangetable entries leftin the parsetree , it becomes a result execution plan (the INSERT ... VALUES version). The rule action above can truely result in both variants. pg_rules システムビューの出力を信用しては行けません。 INSERT における NEW と OLD のみを参照する状態を取り扱い、 INSERT の VALUES フォーマットを出力するものです。 パースツリーのレベルでは INSERT ... VALUE と INSERT ... SELECT の間に違いはありません。それらは共に 範囲テーブル、目的リストそしてたぶん条件その他を持っています。 オプティマイザは、型の結果、 逐次スキャン、インデックススキャン、 ジョインあるいはそのパースツリーのどれにでもに対する実行プランを 生成するかどうか後に決定します。パースツリーに範囲テーブル項目 を参照するものが残っていない場合、それが結果として (INSERT ... VALUES バージョンの)実行プランになります。 このルールアクションは二つの異なった結果を実際引き起こします。

The rule is a qualified non-INSTEAD rule, so the rule system has to return two parsetrees. The modified rule action and the original parsetree. In the first step the rangetable of the original query is incorporated into the rules action parsetree. This results in

ルールは非 INSTEAD ルールで条件づけられるため、ルールシステム は二つのパースツリーを戻さなければなりません。変更されたルール アクションと元のパースツリーです。第一のステップで元の問合せの 範囲テーブルはルールアクションパースツリーと関連づけられます。 そして、次の結果を生みます。

    INSERT INTO shoelace_log SELECT 
           *NEW*.sl_name, *NEW*.sl_avai,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data shoelace_data, shoelace_data *NEW*,
           shoelace_data *OLD*, shoelace_log shoelace_log;
In step 2 the rule qualification is added to it, so the result set is restricted to rows where sl_avail changes. ステップ 2 で、ルールの条件が付け加えられ、結果セットは sl_avail が変更された行に限定されます。
    INSERT INTO shoelace_log SELECT 
           *NEW*.sl_name, *NEW*.sl_avai,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data shoelace_data, shoelace_data *NEW*,
           shoelace_data *OLD*, shoelace_log shoelace_log
     WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail);
In step 3 the original parsetrees qualification is added, restricting the resultset further to only the rows touched by the original parsetree. ステップ 3 で、元のパースツリーの条件が付け加えられ、 結果セットは更に元のパースツリーに付帯する行のみに制限を かけます。
    INSERT INTO shoelace_log SELECT 
           *NEW*.sl_name, *NEW*.sl_avai,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data shoelace_data, shoelace_data *NEW*,
           shoelace_data *OLD*, shoelace_log shoelace_log
     WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)
       AND bpchareq(shoelace_data.sl_name, 'sl7');
Step 4 substitutes NEW references by the targetlist entries from the original parsetree or with the matching variable references from the result relation. ステップ 4 は、オリジナルのパースツリーの目的リスト項目または 結果リレーションの該当する変数参照で NEW の参照を置換します。
    INSERT INTO shoelace_log SELECT 
           shoelace_data.sl_name, 6,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data shoelace_data, shoelace_data *NEW*,
           shoelace_data *OLD*, shoelace_log shoelace_log
     WHERE int4ne(6, *OLD*.sl_avail)
       AND bpchareq(shoelace_data.sl_name, 'sl7');
Step 5 replaces OLD references into resultrelation references. ステップ 5 は、OLD 参照を結果リレーション参照に置き換えます。
    INSERT INTO shoelace_log SELECT 
           shoelace_data.sl_name, 6,
           getpgusername(), datetime('now'::text)
      FROM shoelace_data shoelace_data, shoelace_data *NEW*,
           shoelace_data *OLD*, shoelace_log shoelace_log
     WHERE int4ne(6, shoelace_data.sl_avail)
       AND bpchareq(shoelace_data.sl_name, 'sl7');
That's it. So reduced to the max the return from the rule system is a list of two parsetrees that are the same as the statements: これで終りです。max まで縮小されて、ルールシステムが戻すのは、 命令文と同じ二つのパースツリーのリストです。
    INSERT INTO shoelace_log SELECT
           shoelace_data.sl_name, 6,
           getpgusername(), 'now'
      FROM shoelace_data
     WHERE 6 != shoelace_data.sl_avail
       AND shoelace_data.sl_name = 'sl7';

    UPDATE shoelace_data SET sl_avail = 6
     WHERE sl_name = 'sl7';
These are executed in this order and that is exactly what the rule defines. The subtitutions and the qualifications added ensure, that if the original query would be an この二つは順番通りに処理され、ルールが定義したことと全く 同一です。追加された置換と条件はオリジナルのの問合せが下記 のようであったか確認します。
    UPDATE shoelace_data SET sl_color = 'green'
     WHERE sl_name = 'sl7';
No log entry would get written because due to the fact that this time the original parsetree does not contain a targetlist entry for sl_avail, NEW.sl_avail will get replaced by shoelace_data.sl_avail resulting in the extra query 今回は、sl_avail に対する目的リスト項目がオリジナルの パースツリーには存在しないのでログ項目は書き出されず、 NEW.sl_avail が shoelace_data.sl_avail に置き換えられて、 特別の問合せになります。
    INSERT INTO shoelace_log SELECT
           shoelace_data.sl_name, shoelace_data.sl_avail,
           getpgusername(), 'now'
      FROM shoelace_data
     WHERE shoelace_data.sl_avail != shoelace_data.sl_avail
       AND shoelace_data.sl_name = 'sl7';
and that qualification will never be true. Since the is no difference on parsetree level between an INSERT ... SELECT, and an INSERT ... VALUES, it will also work if the original query modifies multiple rows. So if Al would issue the command そして、その条件は真実ではありえません。 パースツリーレベルでは INSERT ... SELECT と INSERT ... VALUES の間に違いがないので、オリジナルの問合せが複数の行に変更を加えても 同じように動作します。そこで、アルが以下のコマンドを入れたとします。
    UPDATE shoelace_data SET sl_avail = 0
     WHERE sl_color = 'black';
four rows in fact get updated (sl1, sl2, sl3 and sl4). But sl3 already has sl_avail = 0. This time, the original parsetrees qualification is different and that results in the extra parsetree 確かに四つの行 (sl1, sl2, sl3 and sl4) が更新されました。 しかし、 sl3 は既に sl_avail = 0 を所有しています。 ここでは、オリジナルのパースツリー条件は異なっていて、特別の パースツリーになります。
    INSERT INTO shoelace_log SELECT
           shoelace_data.sl_name, 0,
           getpgusername(), 'now'
      FROM shoelace_data
     WHERE 0 != shoelace_data.sl_avail
       AND shoelace_data.sl_color = 'black';
This parsetree will surely insert three new log entries. And that's absolutely correct. このパースツリーは確実に三つの新しいログ項目を挿入します。 これは全く正しい動作です。

It is important, that the original parsetree is executed last. The Postgres "traffic cop" does a command counter increment between the execution of the two parsetrees so the second one can see changes made by the first. If the UPDATE would have been executed first, all the rows are already set to zero, so the logging INSERT would not find any row where 0 != shoelace_data.sl_avail.

オリジナルのパースツリーが最後に実行されるということは重要です。 Postgres の"交通警官"は、二つの パースツリーの実行の間のコマンドカウンタを初めの実行による 変更を二番目が解るように増やします。もし UPDATE が先に 実行されたとしたら、全ての行はゼロにセットされて、 0 != shoelace_data.sl_avail である行を INSERT の段階で 見つけ出せなくなります。

ビューとの協調

A simple way to protect view relations from the mentioned possibility that someone can INSERT, UPDATE and DELETE invisible data on them is to let those parsetrees get thrown away. We create the rules

どこかのユーザが見えないデータにたいし INSERT、UPDATE および DELETE を発行するといった、既述の可能性からビューリレーション を守る簡単な方法はそれらのパースツリーを破棄してしまうことです。 ルールを作ります。

    CREATE RULE shoe_ins_protect AS ON INSERT TO shoe
        DO INSTEAD NOTHING;
    CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe
        DO INSTEAD NOTHING;
    CREATE RULE shoe_del_protect AS ON DELETE TO shoe
        DO INSTEAD NOTHING;
If Al now tries to do any of these operations on the view relation shoe, the rule system will apply the rules. Since the rules have no actions and are INSTEAD, the resulting list of parsetrees will be empty and the whole query will become nothing because there is nothing left to be optimized or executed after the rule system is done with it. アルがビューのリレーション shoe に 上記の操作を加えようとすると、ルールシステムはルールを適用 します。ルールにはアクションがなく INSTEAD ですから、結果の パースツリーリストは空で、ルールシステムの処理が完了した後に 最適化されるものや実行されるべきものが何も残っていませんので、 全ての問合せは無効となります。

注意事項: This fact might irritate frontend applications because absolutely nothing happened on the database and thus, the backend will not return anything for the query. Not even a PGRES_EMPTY_QUERY or so will be available in libpq. In psql, nothing happens. This might change in the future.

この事実はデータベースに何の変化ももたらさず、従いバック エンドは問合せに対する回答を何も戻さないため、 フロントエンドアプリケーションがじらされる可能性があります。 libpq では PGRES_EMPTY_QUERY だけでなく似たようなものも 許容されます。psql では何も起こりません。将来変更される かもしれません。

A more sophisticated way to use the rule system is to create rules that rewrite the parsetree into one that does the right operation on the real tables. To do that on the shoelace view, we create the following rules:

より洗練されたルールシステムの使用法は、パースツリーを 実テーブルに正確な操作を行うものに書き換える事です。 shoelace ビューにこのことを適用する ため以下のルールを作ります。

    CREATE RULE shoelace_ins AS ON INSERT TO shoelace
        DO INSTEAD
        INSERT INTO shoelace_data VALUES (
               NEW.sl_name,
               NEW.sl_avail,
               NEW.sl_color,
               NEW.sl_len,
               NEW.sl_unit);

    CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
        DO INSTEAD
        UPDATE shoelace_data SET
               sl_name = NEW.sl_name,
               sl_avail = NEW.sl_avail,
               sl_color = NEW.sl_color,
               sl_len = NEW.sl_len,
               sl_unit = NEW.sl_unit
         WHERE sl_name = OLD.sl_name;

    CREATE RULE shoelace_del AS ON DELETE TO shoelace
        DO INSTEAD
        DELETE FROM shoelace_data
         WHERE sl_name = OLD.sl_name;
Now there is a pack of shoelaces arriving in Al's shop and it has a big partlist. Al is not that good in calculating and so we don't want him to manually update the shoelace view. Instead we setup two little tables, one where he can insert the items from the partlist and one with a special trick. The create commands for anything are: 今、アルの店に靴紐のケースが分厚い送り状とともに届けられました。 アルは計算が得意でないので、 shoelace のビューの更新を手作業で やらせるわけには行きません。代わりに、送り状から品目を挿入する テーブルと特殊な仕掛けのテーブルの二つの小さなテーブルを用意し ました。
    CREATE TABLE shoelace_arrive (
        arr_name    char(10),
        arr_quant   integer
    );

    CREATE TABLE shoelace_ok (
        ok_name     char(10),
        ok_quant    integer
    );

    CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok
        DO INSTEAD
        UPDATE shoelace SET
               sl_avail = sl_avail + NEW.ok_quant
         WHERE sl_name = NEW.ok_name;
Now Al can sit down and do whatever until アルのすることは特にありません。以下の結果を入手する まで何をしていても構いません。
    al_bundy=> SELECT * FROM shoelace_arrive;
    arr_name  |arr_quant
    ----------+---------
    sl3       |       10
    sl6       |       20
    sl8       |       20
    (3 rows)
is exactly that what's on the part list. We take a quick look at the current data, 結果は送り状に記載されたものと全く同じになっています。 現在のデータをちょっと見てみます。
    al_bundy=> SELECT * FROM shoelace ORDER BY sl_name;
    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       |       6|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)
move the arrived shoelaces in 入荷した靴紐のデータを移動させます。
    al_bundy=> INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
and check the results 結果をチェックします。
    al_bundy=> SELECT * FROM shoelace ORDER BY sl_name;
    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       |       6|brown     |    60|cm      |       60
    sl4       |       8|black     |    40|inch    |    101.6
    sl3       |      10|black     |    35|inch    |     88.9
    sl8       |      21|brown     |    40|inch    |    101.6
    sl5       |       4|brown     |     1|m       |      100
    sl6       |      20|brown     |   0.9|m       |       90
    (8 rows)

    al_bundy=> SELECT * FROM shoelace_log;
    sl_name   |sl_avail|log_who|log_when                        
    ----------+--------+-------+--------------------------------
    sl7       |       6|Al     |Tue Oct 20 19:14:45 1998 MET DST
    sl3       |      10|Al     |Tue Oct 20 19:25:16 1998 MET DST
    sl6       |      20|Al     |Tue Oct 20 19:25:16 1998 MET DST
    sl8       |      21|Al     |Tue Oct 20 19:25:16 1998 MET DST
    (4 rows)
It's a long way from the one INSERT ... SELECT to these results. And it's description will be the last in this document (but not the last example :-). First there was the parsers output 一つの INSERT ... SELECT から三つの結果まで長い道のりでした。 このドキュメントでの解説はこれが最後です。(最後の例ではありま せんが。) 初めにパーサの出力があって、
    INSERT INTO shoelace_ok SELECT
           shoelace_arrive.arr_name, shoelace_arrive.arr_quant
      FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
Now the first rule 'shoelace_ok_ins' is applied and turns it into 最初のルール 'shoelace_ok_ins' が適用され、結果は
    UPDATE shoelace SET
           sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant)
      FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
           shoelace_ok *OLD*, shoelace_ok *NEW*,
           shoelace shoelace
     WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name);
and throws away the original INSERT on shoelace_ok. This rewritten query is passed to the rule system again and the second applied rule 'shoelace_upd' produced となって、shoelace_ok に対する元の INSERT を破棄します。この書き換えられた問合せは再びルールシステムに わたされて、二番目に適用される 'shoelace_upd' ルールが生成されました。
    UPDATE shoelace_data SET
           sl_name = shoelace.sl_name,
           sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant),
           sl_color = shoelace.sl_color,
           sl_len = shoelace.sl_len,
           sl_unit = shoelace.sl_unit
      FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
           shoelace_ok *OLD*, shoelace_ok *NEW*,
           shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data showlace_data
     WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name)
       AND bpchareq(shoelace_data.sl_name, shoelace.sl_name);
Again it's an INSTEAD rule and the previous parsetree is trashed. Note that this query sill uses the view shoelace But the rule system isn't finished with this loop so it continues and applies the rule '_RETshoelace' on it and we get 再び INSTEAD ルールで、以前のパースツリーは破棄されます。 この問合せは view shoelace を引続き使用 しますが、このループによるルールシステムは終了していないので ルール '_RETshoelace' の適用を継続して下記を得ます。
    UPDATE shoelace_data SET
           sl_name = s.sl_name,
           sl_avail = int4pl(s.sl_avail, shoelace_arrive.arr_quant),
           sl_color = s.sl_color,
           sl_len = s.sl_len,
           sl_unit = s.sl_unit
      FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
           shoelace_ok *OLD*, shoelace_ok *NEW*,
           shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data showlace_data,
           shoelace *OLD*, shoelace *NEW*,
           shoelace_data s, unit u
     WHERE bpchareq(s.sl_name, showlace_arrive.arr_name)
       AND bpchareq(shoelace_data.sl_name, s.sl_name);
Again an update rule has been applied and so the wheel turns on and we are in rewrite round 3. This time rule 'log_shoelace' gets applied what produces the extra parsetree 再度更新ルールが適用されました。方向転換をして書き換え 第三ラウンドに突入です。ここではルール 'log_shoelace' が 特別のパースツリーを生成したものに適用されます。
    INSERT INTO shoelace_log SELECT
           s.sl_name,
           int4pl(s.sl_avail, shoelace_arrive.arr_quant),
           getpgusername(),
           datetime('now'::text)
      FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
           shoelace_ok *OLD*, shoelace_ok *NEW*,
           shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data showlace_data,
           shoelace *OLD*, shoelace *NEW*,
           shoelace_data s, unit u,
           shoelace_data *OLD*, shoelace_data *NEW*
           shoelace_log shoelace_log
     WHERE bpchareq(s.sl_name,  showlace_arrive.arr_name)
       AND bpchareq(shoelace_data.sl_name, s.sl_name);
       AND int4ne(int4pl(s.sl_avail, shoelace_arrive.arr_quant),
                                                    s.sl_avail);
After that the rule system runs out of rules and returns the generated parsetrees. So we end up with two final parsetrees that are equal to the SQL statements この後、ルールシステムはルールを使いきって生成されたパースツリー を返します。そして、SQL 文と同じ二つの 最終パースツリーで終結します。
    INSERT INTO shoelace_log SELECT
           s.sl_name,
           s.sl_avail + shoelace_arrive.arr_quant,
           getpgusername(),
           'now'
      FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data,
           shoelace_data s
     WHERE s.sl_name = shoelace_arrive.arr_name
       AND shoelace_data.sl_name = s.sl_name
       AND s.sl_avail + shoelace_arrive.arr_quant != s.sl_avail;
           
    UPDATE shoelace_data SET
           sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant
     FROM shoelace_arrive shoelace_arrive,
          shoelace_data shoelace_data,
          shoelace_data s
    WHERE s.sl_name = shoelace_arrive.sl_name
      AND shoelace_data.sl_name = s.sl_name;
The result is that data coming from one relation inserted into another, changed into updates on a third, changed into updating a fourth plus logging that final update in a fifth gets reduced into two queries. 結果は、一つのリレーションから来たデータが別のリレーションに 挿入され、三つ目のリレーションの更新に変更され、四つ目の ために更新され、五つ目の最終更新のログ記録となって、最終的に 二つの問合せに縮小されます。

There is a little detail that's a bit ugly. Looking at the two queries turns out, that the shoelace_data relation appears twice in the rangetable where it could definitely be reduced to one. The optimizer does not handle it and so the execution plan for the rule systems output of the INSERT will be

ちょっと醜い些細な事項があります。出来て来た二つの問合せを 見ると、一つに縮小されたはずの shoelace_data リレーションが範囲テーブルに二度出て来ます。オプティマイザは処理 をしないので、INSERT のルールシステムの出力に対する実行プランは 次のようになります。

Nested Loop
  ->  Merge Join
        ->  Seq Scan
              ->  Sort
                    ->  Seq Scan on s
        ->  Seq Scan
              ->  Sort
                    ->  Seq Scan on shoelace_arrive
  ->  Seq Scan on shoelace_data
ネストしたループ
  ->  マージ ジョイン
        ->  逐次スキャン
              ->  ソート
                    ->  s を逐次スキャン
        ->  逐次スキャン
              ->  ソート
                    ->  shoelace_arrive を逐次スキャン
  ->  shoelace_data を逐次スキャン
while omitting the extra rangetable entry would result in a 一方特別な範囲テーブル項目を落すことで
Merge Join
  ->  Seq Scan
        ->  Sort
              ->  Seq Scan on s
  ->  Seq Scan
        ->  Sort
              ->  Seq Scan on shoelace_arrive
that totally produces the same entries in the log relation. Thus, the rule system caused one extra scan on the shoelace_data relation that is absolutely not necessary. And the same obsolete scan is done once more in the UPDATE. But it was a really hard job to make that all possible at all.
マージ ジョイン
  ->  逐次スキャン
        ->  ソート
              ->  s を逐次スキャン
  ->  逐次スキャン
        ->  ソート
              ->  shoelace_arrive を逐次スキャン
の様にログリレーションに全く同じ項目が作られます。 ですから、ルールシステムは全く必要のない shoelace_data リレーションに対する 一つの特別なスキャンを行うことになります。そして UPDATE でも同様な不要のスキャンが再度実行されます。しかしながら、 これら全てを適していることにするのは大変な仕事です。

A final demonstration of the Postgres rule system and it's power. There is a cute blonde that sells shoelaces. And what Al could never realize, she's not only cute, she's smart too - a little too smart. Thus, it happens from time to time that Al orders shoelaces that are absolutely not sellable. This time he ordered 1000 pairs of magenta shoelaces and since another kind is currently not available but he committed to buy some, he also prepared his database for pink ones.

Postgres のルールシステムとその 効力にたいする最後の実証です。靴紐を売っている可愛いブロンド がいます。アルが判っていないことは、可愛いだけでなく賢い子 だということです。とても賢明な少女です。ですから、時として アルは全く売れない靴紐を注文してしまいます。 今回はマゼンタ色の靴紐 1000 組を注文しましたし、ほかの種類 が現在在庫に無いから、それは後で購入する約束までしました。 データベースにはピンクの靴紐を用意しました。

    al_bundy=> INSERT INTO shoelace VALUES 
    al_bundy->     ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
    al_bundy=> INSERT INTO shoelace VALUES 
    al_bundy->     ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
Since this happens often, we must lookup for shoelace entries, that fit for absolutely no shoe sometimes. We could do that in a complicated statement every time, or we can setup a view for it. The view for this is こんなことがたびたび起こるので、お店にない靴用の靴紐の項目 を見なくてはなりません。毎回複雑な構文を打ち込むか、または ビューを作ります。ビューはこうなります。
    CREATE VIEW shoelace_obsolete AS
        SELECT * FROM shoelace WHERE NOT EXISTS
            (SELECT shoename FROM shoe WHERE slcolor = sl_color);
It's output is その出力は以下となります。
    al_bundy=> SELECT * FROM shoelace_obsolete;
    sl_name   |sl_avail|sl_color  |sl_len|sl_unit |sl_len_cm
    ----------+--------+----------+------+--------+---------
    sl9       |       0|pink      |    35|inch    |     88.9
    sl10      |    1000|magenta   |    40|inch    |    101.6
For the 1000 magenta shoelaces we must debt Al before we can throw 'em away, but that's another problem. The pink entry we delete. To make it a little harder for Postgres, we don't delete it directly. Instead we create one more view 1000 のマゼンタ色の靴紐を捨てる前にアルに債務を負わせなければ なりませんが、それは別の問題です。ピンクの項目を削除します。 Postgres でちょっと面倒なことは、 直接削除をしないと言うことです。代わりにもう一つ別のビューを 作ります。
    CREATE VIEW shoelace_candelete AS
        SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;
and do it this way: このようにします。
    DELETE FROM shoelace WHERE EXISTS
        (SELECT * FROM shoelace_candelete
                 WHERE sl_name = shoelace.sl_name);
Voila: さあ、出来ました。
    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       |       6|brown     |    60|cm      |       60
    sl4       |       8|black     |    40|inch    |    101.6
    sl3       |      10|black     |    35|inch    |     88.9
    sl8       |      21|brown     |    40|inch    |    101.6
    sl10      |    1000|magenta   |    40|inch    |    101.6
    sl5       |       4|brown     |     1|m       |      100
    sl6       |      20|brown     |   0.9|m       |       90
    (9 rows)
A DELETE on a view, with a subselect qualification that in total uses 4 nesting/joined views, where one of them itself has a subselect qualification containing a view and where calculated view columns are used, gets rewritten into one single parsetree that deletes the requested data from a real table. ビュー上での合計四つのネスト/ジョインされたビューで、 その中の一つはビューを含む subselect 条件があって、かつ演算 を施されたビューのカラムが使われる場合、subselect 条件のつい たビューへの DELETE は実テーブルから、要求されたデータを削除 する単一のパースツリーに書き換えられます。

I think there are only a few situations out in the real world, where such a construct is necessary. But it makes me feel comfortable that it works.

このような構造が必要な状況は実社会ではほとんど無いと考え ますが、実際に動くことを確認するのは快いものです。

The truth is: Doing this I found one more bug while writing this document. But after fixing that I was a little amazed that it works at all.

こんなことをやっていて、このドキュメントを書いている途中でもう 一つのバグを見つけました。バグを修正すると完璧に動いたので ちょっとびっくりしました。