INSERT、 UPDATE および DELETE でのルールは前節で解説したビューの ルールとは全く異なります。第一点として、これらの CREATE RULE コマンド では以下のケースがあり得ます。
ルールがアクションを持つことができない場合。
ルールが複数のアクションを持てる場合。
キーワード INSTEAD がオプションの場合。
疑似リレーション NEW と OLD が使える場合。
ルールがルール条件を所有できる場合。
CREATE RULE rule_name AS ON event TO object [WHERE rule_qualification] DO [INSTEAD] [action | (actions) | NOTHING];上記の構文を覚えておいて下さい。以下では、"更新ルール" は INSERT、 UPDATE または DELETE に定義されたルールを意味します。
更新ルールはルールシステムによって、結果リレーションと構文解析 ツリーのコマンドタイプが CREATE RULE で与えられる オブジェクトとイベントと等しい場合に適用されます。更新ルールに対して ルールシステムは構文解析ツリーのリストを生成します。最初は構文解析 ツリーリストは空です。ゼロ(キーワードが NOTHING) か、一つまたは複数のアクションが有効です。簡単にするため、ここでは 一つのアクションのルールを取り上げます。このルールは条件を持っていても 持っていなくても、また INSTEAD であっても、無くても構いません。
ルール条件とはどんなものでしょうか?それはルールのアクションを行わなければ ならない時と、行ってはならない時を指定する条件です。基本的に (特別な意味合いを持った)オブジェクトとして与えられるリレーションである NEW そして、あるいは OLD 疑似リレーションのみを、この条件は参照 します。
一つのアクションルールに対し、以下の構文解析ツリーを生成する 四つの事例が挙げられます。
条件なしで INSTEAD でない場合:
オリジナル構文解析ツリーの条件が付け加えられた ルールアクションからの構文解析ツリー
条件なしで INSTEAD の場合:
オリジナル構文解析ツリーの条件が付け加えられたルール アクションからの構文解析ツリー
条件付きで INSTEAD でない場合:
ルール条件とオリジナル構文解析ツリーの条件が付け加えられた ルールアクションからの構文解析ツリー
条件つきで INSTEAD の場合:
ルール条件とオリジナル構文解析ツリーの条件が付け加えられた ルールアクションからの構文解析ツリー
否定されたルール条件が付け加えられたオリジナルの 構文解析ツリー
最後に、もしルールが INSTEAD でない場合、変更されていないオリジナルの 構文解析ツリーがリストに付け加えられます。条件に合った INSTEAD ルール のみが既にオリジナルの構文解析ツリーに追加をしているので、最後は 一つのアクションに対して最大合わせて二つの構文解析ツリーに行きつきます。
ルールアクションで生成された構文解析ツリーは再度書き換えシステムに 手渡され、相当数の構文解析ツリーの結果をもたらす、より多くのルール の適用をうけることもあります。ですから、ルールアクションにおける 構文解析ツリーは別のコマンドタイプか、別の結果リレーションを 持っていなければなりません。さもないとこの再帰的手順はループに なってしまいます。現在 10 回までの再帰処理反復制限が組み込まれて います。10 回反復処理を行った後にもまだ適用すべき更新ルールが あった場合は、ルールシステムは複数のルール定義にまたがったループ であると想定し、そのトランザクションを停止します。
pg_rewriteシステムカタログのアクション にある構文解析ツリーは単なるテンプレートです。これらは NEW と OLD に対する範囲テーブルの項目を参照することが出来るため 使用される前に何らかの置換措置がとられていなければなりません。 NEW に対するどんな参照でも、オリジナルの問い合わせの目的リストは 関連する項目があるかどうか検索されます。項目が見つかった場合には、 その項目式が参照と置き換えられます。項目がなかった場合、(UPDATE では) NEW は OLD と同じ意味になるか(INSERT では) NULL によって置き換え られます。OLD に対する参照はすべて結果リレーションである範囲 テーブルの項目に対する参照に置き換えられます。
更新ルールの適用が終ると、そこで作られた構文解析ツリーにビュールール を適用します。ビューは新しい更新アクションを挿入できないため、 ビュー書き換えの結果に更新ルールを適用する必要はありません。
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 text, -- who did it log_when timestamp -- 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, 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');ルールの条件式を持つ ON UPDATE の 'log_shoelace' ルールと、
int4ne(NEW.sl_avail, OLD.sl_avail)一つのアクション
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 ルールで条件づけられるため、ルールシステムは 二つの構文解析ツリーを戻さなければなりません。変更されたルール アクションと元の構文解析ツリーです。第一の段階で元の問い合わせの 範囲テーブルはルールアクション構文解析ツリーに取りこまれます。 そして、次の結果を生みます。
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;第二段階で、ルールの条件が付け加えられ、結果セットは 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 のために同じ機能をサポートしています。 第三ステップで、元の構文解析ツリーの条件が付け加えられ、 結果セットは更に元の構文解析ツリーが処理対象とする行のみに制限を かけます。
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');第四段階は、オリジナルの構文解析ツリーの目的リスト項目または 結果リレーションの該当する変数参照で 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');第五段階は、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 ではないため、オリジナルの 構文解析ツリーも出力します。簡単に言えば、ルールシステムからの 出力はステートメントと同じ二つの構文解析ツリーのリストです。
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';この二つは順番通りに処理され、正確にルールが定義した通りです。 追加された置換と条件はオリジナルの問い合わせが下記のようであったか 確認します。
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 and 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';この構文解析ツリーは確実に三つの新しいログ項目を挿入します。 これは全く正しい動作です。
オリジナルの構文解析ツリーが最後に実行されるということは重要です。 Postgresの "交通警官" は、二つの 構文解析ツリーの実行の間のコマンドカウンタを増やし、最初のパースツリー が行った変更が二番目のパースツリーから見えるようにします。 もし UPDATE が先に実行されたとしたら、 全ての行はゼロにセットされて、0 != shoelace_data.sl_avail である 行を INSERT の段階で見つけ出せなくなります。
どこかのユーザが見えないデータに対し 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 ですから、結果の構文解析ツリー リストは空で、ルールシステムの処理が完了した後に最適化されるものや 実行されるべきものが何も残っていませんので、全ての問い合わせは 無効となります。
注釈: この事実はデータベースに何の変化ももたらさず、したがってバックエンド は問い合わせに対する回答を何も返さないため、フロントエンド アプリケーションが問題を起こす可能性があります。libpq では PGRES_EMPTY_QUERY さえも返却されません。 psql では何も起こりません。これは将来変更されるかもしません。
より洗練されたルールシステムの使用法は、実テーブルに適当な 操作を行うパースツリーへの書き換えを行うルールを作ることです。 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 のビューの更新を手作業で やらせるわけにはいきません。代わりに、送り状から品目を挿入する テーブルと特殊なしかけのテーブルの二つの小さなテーブルを用意しました。 それらの create コマンドは以下です。
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 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)入荷した靴紐のデータを移動させます。
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)ひとつの 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 を 破棄します。この書き換えられた問い合わせは再びルールシステムに 渡されて、二番目に適用される '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を引き続き使用しますが、 このループによるルールシステムは終了していないのでルール '_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);再度更新ルールが適用されました。方向転換をして書き換え第三 ラウンドに突入です。ここではルール '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 文と同じ二つの最終 構文解析ツリーで終結します。
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;結果は、一つのリレーションから来たデータが別のリレーションに挿入され、 三つめのリレーションの更新に変更され、四つ目のために更新され、 五つ目の最終更新のログ記録となって、最終的に二つの問い合わせに 縮小されます。
ちょっと見苦しい小さな事項があります。出来てきた二つの問い合わせを 見ると、一つに縮小されたはずのshoelace_data リレーションが範囲テーブルに二度出てきます。オプティマイザは処理を しないので、INSERT のルールシステムの出力に対する実行プランは 次のようになります。
ネストしたループ -> マージ結合 -> 順スキャン -> ソート -> s を順スキャン -> 順スキャン -> ソート -> shoelace_arrive を順スキャン -> shoelace_data を順スキャン一方特別な範囲テーブル項目を落すことで
マージ結合 -> 順スキャン -> ソート -> s を順スキャン -> 順スキャン -> ソート -> shoelace_arrive を順スキャンの様にログリレーションに全く同じ項目が作られます。ですから、 ルールシステムは全く必要の無いshoelace_data リレーションに対する一つの特別なスキャンを行うことになります。 そして UPDATE でも同様な不用のスキャンが再度実行されます。 しかしながら、これらをすべて可能にするのは大変な仕事です。
最後に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);こんなことがたびたび起こるので、お店に無い靴用の靴紐の項目を 見なくてはなりません。毎回複雑な構文を打ち込むか、またはビューを 作ります。ビューはこうなります。
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.61000 のマゼンタ色の靴紐を捨てる前にアルに債務をおわせなければ なりませんが、それは別の問題です。ピンクの項目を削除します。 Postgresでちょっと面倒なことは、直接 削除をしないということです。代わりにもう一つ別のビューを作ります。
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)ビュー上での合計四つのネスト/結合されたビューで、その中の 一つはビューを含む subselect 条件があって、かつ演算を施された ビューのカラムが使われる場合、subselect 条件のついたビューへの DELETE は実テーブルから、要求されたデータを削除する単一の 構文解析ツリーに書き換えられます。
このような構造が必要な状況は実社会ではほとんどないと思われますが、 実際に動くことを確認できれば安心できます。
実は: このドキュメントを書きながらこのようなことをしていて、もう一つ のバグを見つけました。バグを修正するとちゃんと動いたので驚きました。