13.4. INSERT、UPDATE、DELETEについてのルール

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

ON INSERT、UPDATE、DELETEでのルールは前節で解説したビューのルールとはまったく異なります。第一点として、これらのCREATE RULEコマンドでは以下のケースがあり得ます。

第二点として、その場所で構文解析ツリーを変更しません。そのかわりに新規の複数(0の場合もありますが)の構文解析ツリーを生成して、オリジナルを破棄します。

13.4.2. これらのルールの動作

CREATE RULE rule_name AS ON event
    TO object [WHERE rule_qualification]
    DO [INSTEAD] [action | (actions) | NOTHING];

上記の構文を覚えておいてください。以下では、更新ルールはINSERT、UPDATE、DELETEに定義されたルールを意味します。

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

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

1つのアクションルールに対し、以下の構文解析ツリーを生成する4つの事

最後に、もしルールがINSTEADでない場合、変更されていない元の構文解析ツリーがリストに付け加えられます。条件付きの INSTEAD ルールのみがすでに元の構文解析ツリーに追加をしているので、最後は 1 つのアクションを持つルールに対して最大合わせて 2 つの構文解析ツリーにたどり着きます。

ON INSERT ルールでは、元の問い合わせは、(INSTEAD により止められていない限り)ルールによって追加されたアクションの前に実行されます。これにより、アクションは挿入された行(複数可)を参照することができます。しかし、ON UPDATE と ON DELETE ルールでは、元の問い合わせはルールによって追加されたアクションの後に実行されます。これは、アクションが更新される予定の、または、削除される予定の行を参照できることを保証します。 さもないと、条件に一致する行を見つけることができないためにアクションが作動しなくなる可能性が起こります。

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

pg_rewriteシステムカタログのアクションにある構文解析ツリーは単なるテンプレートです。これらはNEWとOLDに対する範囲テーブルの項目を参照することができるため、使用される前に何らかの置換措置がとられていなければなりません。NEWに対するどんな参照でも、元の問い合わせの目的リストは対応する項目があるかどうか検索されます。項目が見つかった場合には、その項目式が参照と置き換えられます。項目がなかった場合、NEWはOLDと同じ意味になる(UPDATEの場合)か、NULLによって置き換えられます(INSERTの場合)。OLDに対する参照はすべて結果リレーションである範囲テーブルの項目に対する参照に置き換えられます。

更新ルールの適用が終わると、そこで作られた構文解析ツリーにビュールールを適用します。 ビューは、新しい更新アクションを挿入できないため、ビュー書き換えの結果に更新ルールを適用する必要はありません。

13.4.2.1. 最初のルール――ステップバイステップ

shoelace_dataリレーションのsl_avail列の変化を追跡してみたいと思います。そこでログ用テーブルと、shoelace_dataに対して行われるUPDATEをログに記録するルールを用意しました。

CREATE TABLE shoelace_log (
    sl_name    char(10),      -- 変更された靴紐
    sl_avail   integer,       -- 新しい現在値
    log_who    text,          -- 誰が行ったか
    log_when   timestamp      -- いつ行ったか
);

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,
                                    current_user,
                                    current_timestamp
                                );

ここでアルは以下を実行します。

al_bundy=> UPDATE shoelace_data SET sl_avail = 6
al_bundy->     WHERE sl_name = 'sl7';

ログテーブルを見てみましょう。

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)

思ったとおりの結果が出ました。以下に裏で何が起こったのかを説明します。パーサがまず以下の構文解析ツリーを生成しました(今回、操作の基本は更新ルールに対するルールアクションのため、元となった構文解析ツリーの部分は強調されています)。

UPDATE shoelace_data SET sl_avail = 6
  FROM shoelace_data shoelace_data
 WHERE bpchareq(shoelace_data.sl_name, 'sl7');

以下のルール条件式

int4ne(NEW.sl_avail, OLD.sl_avail)

および、以下のアクションによる ON UPDATE の log_shoelace ルールがあります。

INSERT INTO shoelace_log VALUES(
       *NEW*.sl_name, *NEW*.sl_avail,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*;

通常ではINSERT ... VALUES ... FROMを書くことはできないのでちょっと奇妙に見えるかもしれません。ここのFROM句は単に*NEW*と*OLD*の構文解析ツリーの範囲テーブル項目があることを示しているだけです。 これらは、INSERT コマンドの問い合わせツリー中の変数から参照されるために必要なのです。

このルールは非INSTEADルールで条件付けられているため、ルールシステムは変更されたルールアクションと元の構文解析ツリーという2つの構文解析ツリーを返さなければなりません。第一の段階で元の問い合わせの範囲テーブルはルールアクション構文解析ツリーに取りこまれます。そして、次の結果を生みます。

INSERT INTO shoelace_log VALUES(
       *NEW*.sl_name, *NEW*.sl_avail,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*,
       shoelace_data shoelace_data;

第2段階で、以下のようにルールの条件が付け加えられます。 これにより、この結果セットはsl_availが変更した行に限定されます。

INSERT INTO shoelace_log VALUES(
       *NEW*.sl_name, *NEW*.sl_avail,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*,
       shoelace_data shoelace_data
 WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail);

INSERT ... VALUESはWHERE句を持たないため、これはさらに奇妙です。しかし、プランナとエクゼキュータには問題ではありません。これらはどのみちINSERT ... SELECTのために同じ機能をサポートしなければなりません。第3ステップで、以下のように元の構文解析ツリーの条件が付け加えられます。

これにより、この結果セットはさらに元の構文解析ツリーが処理対象とする行のみに限定されます。

INSERT INTO shoelace_log VALUES(
       *NEW*.sl_name, *NEW*.sl_avail,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*,
       shoelace_data shoelace_data
 WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)
   AND bpchareq(shoelace_data.sl_name, 'sl7');

第 4 段階では、以下のように元の構文解析ツリーの目的リスト項目、または結果リレーションの該当する変数参照で、NEW の参照を置換します。

INSERT INTO shoelace_log VALUES(
       shoelace_data.sl_name, 6,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*,
       shoelace_data shoelace_data
 WHERE int4ne(6, *OLD*.sl_avail)
   AND bpchareq(shoelace_data.sl_name, 'sl7');

第5段階は、以下のようにOLD参照を結果リレーション参照に置き換えます。

INSERT INTO shoelace_log VALUES(
       shoelace_data.sl_name, 6,
       current_user, current_timestamp)
  FROM shoelace_data *NEW*, shoelace_data *OLD*,
       shoelace_data shoelace_data
 WHERE int4ne(6, shoelace_data.sl_avail)
   AND bpchareq(shoelace_data.sl_name, 'sl7');

これで終わりです。このルールはINSTEADではないため、元の構文解析ツリーも出力します。簡単にいえば、ルールシステムからの出力は以下の文と同じ2つの構文解析ツリーのリストです。

INSERT INTO shoelace_log VALUES(
       shoelace_data.sl_name, 6,
       current_user, current_timestamp)
  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';

この2つは順番どおりに処理され、正確にルールが定義したとおりです。 追加された置換と条件は、元の問い合わせが例えば下記のような場合に、ログには何も書かれないことを確実にします。

UPDATE shoelace_data SET sl_color = 'green'
 WHERE sl_name = 'sl7';

この場合、元の構文解析ツリーの目的リストには sl_availの項目がありませんので、NEW.sl_availがshoelace_data.sl_availに置き換えられて、以下のような特別の問い合わせになります。

INSERT INTO shoelace_log VALUES(
       shoelace_data.sl_name, shoelace_data.sl_avail,
       current_user, current_timestamp)
  FROM shoelace_data
 WHERE shoelace_data.sl_avail != shoelace_data.sl_avail
   AND shoelace_data.sl_name = 'sl7';

そしてその条件は真にはなりえません。もし元の問い合わせが複数の行を変更してもうまくいきます。ですから、たとえばアルが下記のようなコマンドを実行したとします。

UPDATE shoelace_data SET sl_avail = 0
 WHERE sl_color = 'black';

この場合、実際には4行が更新されます(sl1、sl2、sl3、sl4)。しかしs13はすでにsl_avail = 0を持っています。 ここでは、元の構文解析ツリーの条件はこれまでとは違っており、その結果特別の構文解析ツリーは以下のようになります。

INSERT INTO shoelace_log SELECT
       shoelace_data.sl_name, 0,
       current_user, current_timestamp
  FROM shoelace_data
 WHERE 0 != shoelace_data.sl_avail
   AND shoelace_data.sl_color = 'black';

この構文解析ツリーは確実に3つの新しいログ項目を挿入します。これは まったく正しい動作です。 (訳注:s13 行は WHERE 0 != shoelace_data.sl_avail 条件を満たさない(0!=0)ので、実際に更新される4行-1の3行分のログ項目が挿入されます。)

ここで元の構文解析ツリーが最後に実行されるということが重要な理由が判ります。もしUPDATEが先に実行されたとしたら、すべての行は0にセットされて、0 != shoelace_data.sl_availである行をログ書き込み時のINSERTの段階で見つけられなくなります。

13.4.3. ビューとの協調

どこかのユーザーが見えないデータに対し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;

アルがビューのリレーションshoeに上記の操作を加えようとすると、ルールシステムはルールを適用します。ルールにはアクションがなくINSTEADですから、結果の構文解析ツリーリストは空で、ルールシステムの処理が完了した後に最適化されるものや実行されるべきものが何も残っていませんので、すべての問い合わせは無効となります。

より洗練されたルールシステムの使用法は、実テーブルに適当な操作を行うパースツリーへの書き換えを行うルールを作ることです。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;

ここでアルの店に靴紐のケースが分厚い送り状とともに届けられました。アルは計算が得意でないので、shoelaceビューの更新を手作業でさせるわけにはいきません。代わりに、送り状から品目を挿入するテーブルと特殊な仕掛けのテーブルの2つの小さなテーブルを用意しました。以下はそれらを作成するコマンドです。

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;

アルのすることは特にないので、以下が送り状に記載されたものと全く同じになるまでは、何をしていても構いません。

al_bundy=> SELECT * FROM shoelace_arrive;
arr_name  |arr_quant
----------+---------
sl3       |       10
sl6       |       20
sl8       |       20
(3 rows)

現在のデータをちょっと見てみましょう。

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

以下のコマンドで入荷した靴紐のデータを移動させます。

al_bundy=> INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;

そして結果をチェックします。

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)

1つのINSERT ... SELECTからこの結果まで長い道のりでした。このドキュメントでの解説はこれが最後です(最後の例ではありませんが)。始めにパーサの出力があります。

INSERT INTO shoelace_ok SELECT
       shoelace_arrive.arr_name, shoelace_arrive.arr_quant
  FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;

最初のルール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);

そして、shoelace_okに対する元のINSERTを破棄します。この書き換えられた問い合わせは再びルールシステムに渡されて、2番目に適用される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);

これは再びINSTEADルールですので、以前の構文解析ツリーは破棄されます。この問い合わせはshoelaceビューを引き続き使用しています。 しかし、このループにおけるルールシステムは終了していないため、引き続き _RETURN ルールが適用され、下記のようになります。

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

再度更新ルールが適用されました。 方向転換をして、書き換え第 3 ラウンドに突入です。 ここでは、log_shoelace ルールを、特別な構文解析ツリーを生成したものに適用します。

INSERT INTO shoelace_log SELECT
       s.sl_name,
       int4pl(s.sl_avail, shoelace_arrive.arr_quant),
       current_user,
       current_timestamp
  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);

その後、ルールシステムはルールを使いきって生成された構文解析ツリーを返します。そして、以下の SQL文と同じ2つの最終構文解析ツリーで終結します。

INSERT INTO shoelace_log SELECT
       s.sl_name,
       s.sl_avail + shoelace_arrive.arr_quant,
       current_user,
       current_timestamp
  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;

結果は、1つのリレーションから来たデータが別のリレーションに挿入され、3つ目のリレーションの更新に変更され、4つ目の更新と5つ目への最終更新のログ記録に変更され、最終的に2つの問い合わせに縮小されます。

ちょっと見苦しい小さな事項があります。できてきた2つの問い合わせを見ると、1つに縮小されたはずのshoelace_dataリレーションが範囲テーブルに2度出てきます。プランナは処理をしないので、INSERTのルールシステムの出力に対する実行計画は次のようになります。

ネストループ
  ->  マージ結合
        ->  順スキャン
              ->  ソート
                    ->  s を順スキャン
        ->  順スキャン
              ->  ソート
                    ->  shoelace_arrive を順スキャン
  ->  shoelace_data を順スキャン

一方、特別な範囲テーブル項目を省略することで、以下のようにログリレーションにまったく同じ項目が作られます。

マージ結合
  ->  順スキャン
        ->  ソート
              ->  s を順スキャン
  ->  順スキャン
        ->  ソート
              ->  shoelace_arrive を順スキャン

ですから、ルールシステムは、まったく必要のない shoelace_data リレーションに対する余計なスキャンを 1 度行うことになります。そしてUPDATEでも同様な不用のスキャンが再度実行されます。しかしながら、これらをすべて可能にするのは大変な仕事です。

最後にPostgreSQLのルールシステムとその効力を示しましょう。靴紐を売っている可愛いブロンドの女の子がいます。アルがわかっていないことは、彼女は可愛いだけでなくとても賢いということです。ちょっと賢すぎるほどなのです。 ですから、ときとしてアルはまったく売れない靴紐を注文してしまいます。今回はマゼンタ色の靴紐1,000組を注文しましたし、他の種類が現在在庫にないので、それは後で購入する約束までしました。 データベースにはピンクの靴紐を用意しました。

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

こんなことがたびたび起こるので、お店にない靴用の靴紐の項目を見なくてはなりません。毎回複雑な構文を打ち込むか、またはビューを作ります。ビューはこうなります。

CREATE VIEW shoelace_obsolete AS
    SELECT * FROM shoelace WHERE NOT EXISTS
        (SELECT shoename FROM shoe WHERE slcolor = sl_color);

その出力は以下となります。

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

1,000 組のマゼンタ色の靴紐を捨てる前にそれをアルの借方に記入しておかなければなりませんが、それは別の問題です。ピンクの項目を削除します。PostgreSQLでちょっと面倒なことは、直接削除をしないということです。代わりにもう1つ別のビューを作ります。

CREATE VIEW shoelace_candelete AS
    SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;

そしてこのようにします。

DELETE FROM shoelace WHERE EXISTS
    (SELECT * FROM shoelace_candelete
             WHERE sl_name = shoelace.sl_name);

さあ、できました。

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)

ビュー上での合計4つのネスト/結合されたビューで、その中の1つはビューを含むsubselect条件があって、かつ演算を施されたビューのカラムが使われる場合、subselect条件のついたビューへのDELETEは実テーブルから、要求されたデータを削除する単一の構文解析ツリーに書き換えられます。

このような構造が必要な状況は実社会ではほとんどないと思われますが、実際に動くことを確認できれば安心できます。

裏話. このドキュメントを書きながらこのようなことをしていて、もう1つのバグを見つけました。バグを修正するとちゃんと動いたので驚きました。