本章ではPostgreSQLのルールシステムについて説明します。 本番で稼働するルールシステムは概念としては単純ですが、実際に使ってみると、わかりにくいところが少なからずあります。
通常それらはストアドプロシージャとトリガですが、他のいくつかのデータベースシステムは能動的データベースルールを定義しています。 PostgreSQLでは関数とトリガとして実装されています。
ルールシステム(より正確にいうと問い合わせ書き換えルールシステム)はストアドプロシージャとトリガとはまったく異なります。 ルールシステムはルールを参照して問い合わせを修正し、修正した問い合わせを、計画作成と実行のために問い合わせプランナに渡します。 これは非常に強力なため、問い合わせ言語プロシージャ、ビューあるいはバージョンなど多くのパターンで使用することができます。 このルールシステムの基礎理論と能力はOn Rules, Procedures, Caching and Views in Database SystemsおよびA Unified Framework for Version Modeling Using Production Rules in a Database Systemで解説されています。
どのようにルールシステムが機能するかを理解するためには、ルールがどのように起動され、その入力と結果は何かを理解しなければなりません。
ルールシステムは問い合わせパーサとプランナの中間に位置します。 ルールシステムは、入力としてパーサの出力、単一の問い合わせツリー、および、何らかの特別な情報を持つ問い合わせツリーでもあるユーザ定義の書き換えルールをとり、結果として0個以上の問い合わせツリーを生成します。 ルールシステムの入力と出力は常にパーサ自体でも生成することができるもので、参照する対象は基本的にSQL文として表現できるものです。
では問い合わせツリーとは何でしょうか。 それは、SQL文を構成する個々の部品を別々に記憶した、SQL文の内部表現です。 debug_print_parse、debug_print_rewritten、もしくはdebug_print_plan設定パラメータを設定していれば、サーバログ内で問い合わせツリーを見ることができます。 ルールアクションもpg_rewriteシステムカタログ内に問い合わせツリーとして格納されています。 これはログ出力のように整形されていませんが、全く同じ情報を持っています。
問い合わせツリーそのものを読むためにはある程度の経験が必要です。 ルールシステムを理解するためには問い合わせツリーのSQL表現で十分ですので、ここではその読み方までは教えません。
本章の問い合わせツリーのSQL表現形式を読む時に必要なのは、問い合わせツリー構造の中に分解された、ある文の部品を識別できることです。 問い合わせツリーには以下の部品があります。
これはどのコマンド(SELECT、INSERT、UPDATE、DELETE)が構文解析ツリーを作ったかを示す単純な値です。
範囲テーブルは問い合わせで使われるリレーションのリストです。 SELECT文ではこれはFROMキーワードの後で与えられるリレーションになります。
範囲テーブルのそれぞれの項目はテーブルもしくはビューを識別し、問い合わせの別の部品ではどんな名前で呼び出されるかを示します。 問い合わせツリーでは範囲テーブルの項目は名前よりも番号で参照されることが多いため、ここではSQL文とは違い、重複する名前があるかということは問題になりません。 これはルールの範囲テーブルがマージされた後に起こる可能性があります。 本章の例ではその状況を含んでいません。
問い合わせの結果が格納されるリレーションを識別する範囲テーブルへのインデックスです。
SELECT問い合わせは通常は結果リレーションを持ちません。 SELECT INTOの場合は特別ですが、INSERT ... SELECTが付いたCREATE TABLEとほぼ同じですので、ここでは個別には説明しません。
INSERT、UPDATE、DELETE問い合わせでは、結果リレーションは変更が有効になるテーブル(もしくはビュー)です。
目的リストは問い合わせの結果を定義する式のリストです。 SELECTの場合、この式は問い合わせの最終結果を構築するものです。 これらはSELECTとFROMキーワードの間にある式に対応します。 (*は単にリレーションのすべての列名の省略です。 それはパーサによって個別の列に展開されますので、ルールシステムが見ることはありません)。
DELETEコマンドは結果を返しませんので、目的リストは必要ありません。 実際には、プランナが空の目的リストに特別なCTID項目を追加します。 しかしこれは、後に説明しますが、ルールシステムの後に行われます。 ルールシステムでは目的リストは空です。
INSERT問い合わせでは、目的リストは結果リレーションへ行く新規の行を示します。 それはVALUES句かINSERT ... SELECTの中のSELECT句の式です。 書き換え処理の最初のステップでは、元の問い合わせでは割り当てられず、デフォルト値となっている列の目的リストの項目を追加します。 残った列(値が与えられていない列、かつ、デフォルト値をもたない列)はすべて、プランナによって定数NULL式で埋められます。
UPDATEコマンドでは、目的リストは古いものを置き換えるべき新しい行を示します。 ルールシステムではコマンド内のSET column = expression部分にある式だけを持っています。 プランナは、古い行から新しい行へ値をコピーする式を挿入することにより、抜けている列を処理します。 そして、DELETEの場合と同様に、特別なCTID項目を追加します。
目的リストの各項目は、定数値、範囲テーブル内のリレーション中の1つの列を指し示す変数、パラメータ、または、関数呼び出し、定数、変数、演算子などにより作られた式のツリーを保持します。
問い合わせの条件は目的リストの項目に含まれている式によく似た式です。 この式の結果は、最終的な結果の行を得るための(INSERT、UPDATE、DELETEまたはSELECT)演算を実行すべきかどうかを示すブール値です。 それはSQL文の中のWHERE句です。
問い合わせの結合ツリーはFROM句の構造を表します。 SELECT ... FROM a, b, cのような単純な問い合わせでは、結合ツリーは単なるFROM項目のリストです。 なぜならこれらはどんな順番で結合しても構わないためです。 しかしJOIN式、特に外部結合が使われた場合は、その結合が示す順番どおりに結合しなければいけません。 この場合結合ツリーはJOIN式の構造を表します。 特定のJOIN句と関連付けられた制約(ONもしくはUSING式からのもの)はこれらの結合ツリーノードに付加された条件として格納されます。 頂点レベルの WHERE式を頂点レベルの結合ツリー項目に付加された条件として格納することも便利です。 ですから、結合ツリーはSELECTのFROM句とWHERE句の両方を表しているわけです。
ORDER BY句のような、問い合わせツリーのその他の部品は、ここでは取り上げません。 ルールシステムはルールを適用している時にそこで項目を入れ換えることもありますが、これはルールシステムの基本とはあまり関係しません。