上一篇博文我們閱讀了postgresql中查詢分析模塊的源碼。查詢分析模塊對前台送來的命令進行詞法分析、語法分析和語義分析后獲得對應的查詢樹(Query)。在獲得查詢樹之后,程序開始對查詢樹進行查詢重寫處理。
這一篇文章我們進入查詢重寫模塊源碼的閱讀。還記得上一篇文章的那張函數調用關系圖么?不記得沒關系,我再放一遍。

上次的查詢分析模塊走了1~7這些步驟。而查詢重寫模塊即如上圖的標記所示,函數pg_rewrite_query是進行查詢重寫處理的入口函數。該函數定義在src/backend/tcop/postgres.c中。
函數pg_rewrite_query的參數就是查詢分析模塊的返回值(Query)。查詢重寫模塊使用規則系統判斷是否要對查詢樹進行重寫。如果查詢樹中每個對象(或者說目標)被定義了轉換規則,那么程序就會使用該規則重寫查詢樹。這部分的源碼大部分都在src/backend/rewrite文件夾下。
下面將首先介紹查詢重寫模塊中使用的規則系統,在此基礎上介紹查詢重寫源碼的調用路徑。
1.規則系統
毫不誇張地說,規則系統是查詢重寫模塊的核心內容,該模塊正是根據規則系統中描述的規則來決定如何對一個查詢樹進行重寫。規則系統由一些列的規則組成。這些規則都存儲在系統表pg_rewrite中。該表的結構如下圖所示。每一條記錄即為一條規則。

其中每個屬性的意義如下:
屬性名 類型 描述
oid oid 行標識符(隱藏屬性; 必須明確選擇)
rulename name 規則名稱
ev_class oid 使用這條規則的表名稱
ev_type char 規則適用的事件類型:1 = SELECT, 2 = UPDATE, 3 = INSERT, 4 = DELETE
ev_enabled char 控制規則在哪個session_replication_role模塊觸發。 O = 規則在 "origin" 和 "local" 模塊觸發, D = 規則被禁用, R = 規則在 "replica" 模塊觸發, A = 規則總是觸發。
is_instead bool 如果該規則是INSTEAD規則,那么為真
ev_qual pg_node_tree 規則的資格條件的表達式樹(以nodeToString()形式存在)
ev_action pg_node_tree 規則動作的查詢樹(以nodeToString()形式存在)
由此我們可以知道:對於該表中的某條規則(即一條記錄),在該條記錄的ev_class屬性表示該規則適用的表(ev_class)上執行特定的命令(ev_type)且滿足規則的條件表達式(ev_equal),那么就用規則的動作(ev_action)來重寫原始的查詢樹。
根據系統表pg_rewrite中屬性值的不同,規則分為兩種:
- 按照規則適用的命令不同,可以分為SELECT、UPDATE、INSERT和DELETE四種;
- 按照規則的動作方式不同,可以分為INSTEAD規則和ALSO規則。
1.1 SELECT/INSERT/UPDATE/DELETE規則
SELECT/INSERT/UPDATE/DELETE規則通過pg_rewrite表的ev_type屬性來區分。它們的區別如下:
- SELECT規則中只能有一個動作,而且是不帶條件的INSTEAD規則。
而INSERT/UPDATE/DELETE規則來說:
- 可以沒有動作,也可以有多個動作;
- 可以是INSTEAD規則,也可是ALSO規則;
- 可以使用偽關系NEW和OLD;
- 可是使用規則條件;
- 不修改查詢樹,而是重建新的零到多個查詢樹(原始的查詢樹會被丟棄).
1.2 INSTEAD規則和ALSO規則
這兩個規則通過pg_rewrite表的is_instead屬性來區分。
INSTEAD規則就是直接用規則中定義的動作替代原始查詢樹中的對規則所在表的引用(例如對視圖的處理)。
而對於ALSO規則,情況就復雜一些。
- 對於INSERT,原始查詢在規則動作執行之前完成,這樣可以保證規則動作能引用插入的行;
- 對於UPDATE/DALETE,原始查詢在規則動作之后完成,這樣能保證規則動作可以引用將要更新或者刪除的元祖。
1.3 視圖和規則系統
PostgreSQL里的視圖是通過規則系統來實現的。下面的命令:
CREATE VIEW myview AS SELECT * FROM mytab;
實際上和下面兩條命令:
CREATE TABLE myview (same column list as mytab);
CREATE RULE "_RETURN" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
之間本質上沒有區別,因為這就是CREATE VIEW命令在內部實際執行的內容。 這樣做有一些負作用。其中之一就是在PostgreSQL 系統表里的視圖的信息與一般表的信息完全一樣。所以對於查詢分析器來說, 表和視圖之間完全沒有區別。它們是同樣的事物:關系。
具體的細節說明,這里不贅述。因為太占篇幅,寫進來有頭重腳輕之感。而且在postgresql的說明文檔里有詳細的解說。我把鏈接放在這里吧。http://www.postgres.cn/docs/9.5/rules-views.html
1.4 規則系統和觸發器
讀者可能會疑惑,這個規則系統和觸發器的功能非常相似,尤其是在ALSO規則中尤其強烈。的確,他們都可以在某種命令和條件下被激活,執行原始查詢以外的動作。但是他們在本質上是有差別的(敲黑板):
觸發器的執行對象是一個個元組,對涉及的每一個元組都要執行一次操作,而規則是對查詢樹的修改或者重寫,涉及的層次要比觸發器高。因此,如果在一個語句中涉及到多個元組,顯然規則的效率要高得多。同時,觸發器在概念上要比規則簡單,觸發器實現的功能可以用規則實現。
1.5 CREATE RULE命令
了解了以上這些情況,我們在來看看CREATE RULE命令。該命令用來定義一個重寫規則。
命令格式如下:
CREATE [ OR REPLACE ] RULE name AS ON event
TO table_name [ WHERE condition ]
DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
這里的event可以是下列之一:
SELECT | INSERT | UPDATE | DELETE
CREATE RULE定義一個適用於特定表或者視圖的新規則。 CREATE OR REPLACE RULE要么是創建一個新規則, 要么是替換一個表上的同名規則。
PostgreSQL規則系統允許在更新、插入、 刪除時執行一個其它的預定義動作。簡單的說, 規則就是在指定表上執行指定動作的時候,將導致一些額外的動作被執行。 一般來說,如果你只是簡單地創建一個視圖的話,系統會自動將視圖的重寫規則寫到pg_rewrite表中,不需要用戶自己創建,除非你想定制這個重寫規則,可以使用CREATE RULE命令創建新的規則來覆蓋它。
具體的詳細命令使用方法,在postgresql的官方手冊里說的很明白,我就不班門弄斧了,放一個鏈接好了http://www.postgres.cn/docs/9.5/sql-createrule.html。
2.查詢重寫
終於到源碼分析這里了,先不多廢話,這里先上個圖。嘿嘿,自己畫的,大家湊合着看。

我們可以看到,pg_rewrite_query函數通過調用QueryRewrite函數來完成查詢樹的重寫。QueryRewrite函數是查詢重寫模塊的主過程,完成了以下操作:
1) 用非SELECT規則將一個查詢重寫為0個或多個查詢,此處調用RewriteQuery函數;
2) 對上一步得到的查詢分別使用RIR規則進行重寫,此處調用fireRIRrules函數;
3)返回重寫后的查詢樹.
好的,那我們再來看看RewriteQuery函數和fireRIRrules函數。
RewriteQuery函數的作用是執行非SELECT規則(也就是處理SELECT以外的語句)。
1) 該函數首先尋找含有INSERT/UPDATE/DELETE的WITH子句(說白了就是CTE)。如果存在,那么就調用自身遞歸地重寫它們;
2) 對INSERT/UPDATE/DELETE語句,調整它們的TargetList,然后執行對應的重寫規則;
3) 得到0個或多個查詢並返回.
非SELECT規則處理完了,剩下的事交給fireRIRrules函數。fireRIRrules函數的作用是執行SELECT規則的重寫。
1)對查詢樹的每一個范圍表(RTE):如果RTE是子查詢,調用自身遞歸地重寫它們;如果是一個表,並且在查詢樹中被引用了,則調用ApplyRetrieveRule函數處理它;
2)對於公共表表達式(CTE),也遞歸地調用fireRIRrules函數重寫;
3)對於查詢樹的子查詢,調用query_tree_walker遍歷子查詢,調用fireRIRonSubLink函數進行查詢重寫;
4)返回重寫后的查詢樹.
在調用完以上兩個函數,得到一組查詢樹鏈表之后,程序最后再設置下canSetTag字段即大功告成。
查詢重寫部分也就到這里了,主體流程還是蠻清晰的。
3.結束語
這周末總算是把查詢重寫部分的源碼粗粗的看了一下,深感不能拖延,因為你不知道未來會有什么破事在等你,因為承諾了一周一更。這篇就算這周的工作量了,如果這周不是特別閑的話可能下篇只能到下周出了。總之感謝各位的支持。
下一篇就寫寫查詢規划部分的源碼(Planner)吧。
