国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】

這篇具有很好參考價(jià)值的文章主要介紹了【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

聲明:本文的部分內(nèi)容參考了他人的文章。在編寫過程中,我們尊重他人的知識(shí)產(chǎn)權(quán)和學(xué)術(shù)成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。
本文主要參考了《PostgresSQL數(shù)據(jù)庫內(nèi)核分析》一書

查詢重寫

??在前一章中,我們重點(diǎn)介紹了查詢分析的過程,再來回顧一下函數(shù)的調(diào)用關(guān)系。
【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫
??查詢分析從exec_simple_query函數(shù)開始到調(diào)用parse_analyze函數(shù)返回Query結(jié)構(gòu)體結(jié)束,而pg_rewrite_query則是執(zhí)行查詢重寫的入口函數(shù),函數(shù)路徑為:src/backend/tcop/postgres.c。查詢重寫模塊使用規(guī)則系統(tǒng)判斷來進(jìn)行查詢樹的重寫,如果查詢樹中某個(gè)目標(biāo)被定義了轉(zhuǎn)換規(guī)則,則該轉(zhuǎn)換規(guī)則會(huì)被用來重寫查詢樹。

系統(tǒng)規(guī)則

??查詢重寫的核心就是規(guī)則系統(tǒng),而規(guī)則系統(tǒng)則由一系列的規(guī)則組成。系統(tǒng)表pg_rewrite存儲(chǔ)重寫規(guī)則,具體內(nèi)容如表5-10所示。pg_rewrite中的每一個(gè)元組代表一條規(guī)則
【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫
【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫
??在 PostgreSQL 中,表 pg_rewrite系統(tǒng)目錄表之一,用于存儲(chǔ)查詢重寫規(guī)則(Query Rewrite Rule)。查詢重寫規(guī)則是一種機(jī)制,允許用戶定義在查詢執(zhí)行前自動(dòng)將查詢轉(zhuǎn)換成其他形式的規(guī)則。這個(gè)機(jī)制可以幫助用戶實(shí)現(xiàn)查詢優(yōu)化、視圖扁平化、安全性控制等功能。
??pg_rewrite 表存儲(chǔ)了數(shù)據(jù)庫中所有的查詢重寫規(guī)則,它的結(jié)構(gòu)如下:

CREATE TABLE pg_rewrite (
    rulename        name,           -- 規(guī)則名稱
    ev_class        oid,            -- 規(guī)則所屬的表的 OID
    ev_type         "char",         -- 規(guī)則類型(INSERT/UPDATE/DELETE/SELECT)
    ev_enabled      "char",         -- 規(guī)則是否啟用(A表示啟用,D表示禁用)
    is_instead      boolean,        -- 是否是 INSTEAD 規(guī)則
    ev_qual         pg_node_tree,   -- 規(guī)則的 WHERE 條件
    ev_action       pg_node_tree,   -- 規(guī)則的替代動(dòng)作
    ev_actiontype   "char"          -- 替代動(dòng)作的類型(S表示SQL語句,r表示規(guī)則)
);

??查詢重寫規(guī)則的添加、修改和刪除都可以通過 pg_rewrite 表來實(shí)現(xiàn)。通常,規(guī)則是由數(shù)據(jù)庫管理員或開發(fā)人員根據(jù)應(yīng)用程序的需求來定義和維護(hù)的。通過使用查詢重寫規(guī)則,可以實(shí)現(xiàn)許多復(fù)雜的查詢優(yōu)化和數(shù)據(jù)訪問控制策略,從而更好地滿足數(shù)據(jù)庫應(yīng)用的需求。
??是不是只看文字描述很抽象?沒關(guān)系,我們以一個(gè)案例進(jìn)行說明:

假設(shè)我們有一個(gè)簡單的學(xué)生信息管理系統(tǒng),其中包含兩個(gè)表:students(學(xué)生信息表)和scores(學(xué)生成績表)。管理員希望在查詢學(xué)生信息時(shí),只顯示當(dāng)前登錄用戶所管理的學(xué)生信息,而不是所有學(xué)生的信息。

  1. 首先,我們創(chuàng)建兩個(gè)表并插入一些示例數(shù)據(jù):
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT,
    age INTEGER
);

CREATE TABLE scores (
    id SERIAL PRIMARY KEY,
    student_id INTEGER,
    subject TEXT,
    score INTEGER
);

INSERT INTO students (name, age) VALUES
    ('Alice', 20),
    ('Bob', 22),
    ('Charlie', 21);

INSERT INTO scores (student_id, subject, score) VALUES
    (1, 'Math', 90),
    (1, 'Science', 85),
    (2, 'Math', 95),
    (2, 'Science', 88),
    (3, 'Math', 87),
    (3, 'Science', 92);
  1. 現(xiàn)在,我們創(chuàng)建一個(gè)查詢重寫規(guī)則,以限制用戶只能訪問他們所管理的學(xué)生信息。我們假設(shè)用戶信息存儲(chǔ)在一個(gè)名為 users 的表中,包含字段 id(用戶ID)和 managed_students(他們所管理的學(xué)生ID列表)。
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT,
    managed_students INTEGER[]
);

INSERT INTO users (name, managed_students) VALUES
    ('Admin', '{1, 2}'),
    ('Manager', '{2, 3}');
  1. 接下來,我們使用查詢重寫規(guī)則來實(shí)現(xiàn)限制訪問策略:
-- 創(chuàng)建查詢重寫規(guī)則
CREATE RULE restrict_students AS
    ON SELECT TO students
    WHERE EXISTS (
        SELECT 1 FROM users
        WHERE id = current_user
        AND student_id = students.id
    );

-- 將查詢重寫規(guī)則標(biāo)記為啟用
UPDATE pg_rewrite
SET ev_enabled = 'A'
WHERE rulename = 'restrict_students';
  1. 現(xiàn)在,當(dāng)管理員或經(jīng)理查詢學(xué)生信息時(shí),只會(huì)返回他們所管理的學(xué)生信息,而不會(huì)返回其他學(xué)生的信息。例如,當(dāng)管理員(id=1)查詢學(xué)生信息時(shí):
SET ROLE admin;

SELECT * FROM students;

-- 輸出:
-- id |  name   | age
-- ----+---------+-----
--  1  | Alice   |  20
--  2  | Bob     |  22
  1. 同樣,當(dāng)經(jīng)理(id=2)查詢學(xué)生信息時(shí):
SET ROLE manager;

SELECT * FROM students;

-- 輸出:
-- id |  name   | age
-- ----+---------+-----
--  2  | Bob     |  22
--  3  | Charlie |  21

??怎么樣?是不是通過一個(gè)案例就能更加直觀的理解了。

視圖和規(guī)則系統(tǒng)

??視圖和規(guī)則系統(tǒng)是 PostgreSQL 數(shù)據(jù)庫中的兩個(gè)重要特性,用于提供數(shù)據(jù)查詢和重寫查詢的功能。我們來回顧一些視圖的基本概念吧。

視圖(Views):
??視圖是一種虛擬表,它是基于一個(gè)或多個(gè)基本表的查詢結(jié)果的命名查詢。通過創(chuàng)建視圖,你可以將復(fù)雜的查詢封裝成一個(gè)簡單的名稱,并像操作表一樣使用它。視圖不存儲(chǔ)數(shù)據(jù),它只是一個(gè)查詢的定義。每當(dāng)你查詢視圖時(shí),實(shí)際上是執(zhí)行視圖定義中的查詢。視圖使得查詢更加靈活和方便,同時(shí)還可以隱藏底層數(shù)據(jù)結(jié)構(gòu)的細(xì)節(jié)。

??創(chuàng)建視圖的語法為:

CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;

??例如,假設(shè)我們有一個(gè)名為 “students” 的表,其中包含學(xué)生的信息,我們可以創(chuàng)建一個(gè)視圖來顯示只包含特定學(xué)院的學(xué)生信息:

CREATE VIEW computer_science_students AS
SELECT id, name, age
FROM students
WHERE department = 'Computer Science';

??然后我們可以查詢這個(gè)視圖:

SELECT * FROM computer_science_students;

??了解了視圖,我們?cè)賮砜纯词裁词恰耙?guī)則”?

規(guī)則系統(tǒng)(Rules):
??規(guī)則系統(tǒng)允許你在查詢執(zhí)行過程中自動(dòng)重寫查詢。它允許你定義一組規(guī)則,當(dāng)一個(gè)特定的查詢被執(zhí)行時(shí),這些規(guī)則會(huì)被應(yīng)用于查詢,然后將查詢重寫為其他形式。規(guī)則通常用于實(shí)現(xiàn)數(shù)據(jù)安全性、限制用戶訪問和實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)換等需求。
??規(guī)則是由兩部分組成:一個(gè)觸發(fā)器函數(shù)和一個(gè)規(guī)則本身。觸發(fā)器函數(shù)是一個(gè)函數(shù),它在特定事件發(fā)生時(shí)被調(diào)用,并根據(jù)規(guī)則的定義執(zhí)行重寫操作。規(guī)則則定義了要匹配的查詢條件和要應(yīng)用的重寫操作。

創(chuàng)建規(guī)則的語法為:

CREATE OR REPLACE FUNCTION trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    -- 觸發(fā)器函數(shù)的邏輯
    -- 可以根據(jù)需要返回 NEW、OLD 或 NULL
END;
$$ LANGUAGE plpgsql;

CREATE RULE rule_name AS ON event TO table_name
DO INSTEAD (
    -- 重寫查詢的邏輯
    -- 可以調(diào)用觸發(fā)器函數(shù)來執(zhí)行重寫操作
);

??例如,我們可以創(chuàng)建一個(gè)規(guī)則來限制用戶只能查詢與其關(guān)聯(lián)的學(xué)生信息:

CREATE OR REPLACE FUNCTION restrict_students_trigger()
RETURNS TRIGGER AS $$
BEGIN
    IF EXISTS (
        SELECT 1 FROM users WHERE id = current_user AND student_id = NEW.id
    ) THEN
        RETURN NEW;
    ELSE
        RETURN NULL;
    END IF;
END;
$$ LANGUAGE plpgsql;

CREATE RULE restrict_students AS ON INSERT OR UPDATE OR DELETE TO students
DO INSTEAD (
    -- 在這里調(diào)用觸發(fā)器函數(shù)來限制用戶訪問
    RETURN restrict_students_trigger();
);

??以上便是視圖和規(guī)則系統(tǒng)在 PostgreSQL 中的基本概念和用法。視圖用于簡化查詢,而規(guī)則系統(tǒng)用于在查詢執(zhí)行過程中實(shí)現(xiàn)查詢的自動(dòng)重寫。

ASLO型規(guī)則的查詢重寫

??"ALSO"型規(guī)則是 PostgreSQL 中一種特殊類型的規(guī)則,用于在查詢重寫時(shí)添加額外的查詢操作。ALSO型規(guī)則可以在原始查詢執(zhí)行之前或之后執(zhí)行其他查詢,并將它們的結(jié)果合并到最終查詢結(jié)果中。這可以在不修改原始查詢的情況下,對(duì)查詢結(jié)果進(jìn)行進(jìn)一步的處理或增加附加的查詢操作。
ALSO型規(guī)則的語法如下:

CREATE RULE rule_name AS
    ON event_name TO table_name
    DO ALSO (
        -- 添加額外的查詢操作
    );

??其中,rule_name 是規(guī)則的名稱,event_name 是觸發(fā)規(guī)則的事件類型,可以是 SELECT、INSERT、UPDATE 或 DELETEtable_name 是規(guī)則所應(yīng)用的表名。DO ALSO 后跟著要執(zhí)行的額外查詢操作。
??只看定義可能還是不好理解,那我們依舊以一個(gè)案例來更加詳細(xì)的說一說。下面是一個(gè)示例,演示如何使用ALSO型規(guī)則實(shí)現(xiàn)在查詢結(jié)果中同時(shí)返回學(xué)生的成績信息。

  1. 假設(shè)有兩個(gè)表:students 存儲(chǔ)學(xué)生信息scores 存儲(chǔ)學(xué)生的成績信息。students 表的主鍵是 idscores 表有一個(gè)外鍵 student_id 關(guān)聯(lián)到 students 表的 id。
    首先,我們創(chuàng)建這兩個(gè)表并插入一些示例數(shù)據(jù):
-- 創(chuàng)建 students 表
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    age INTEGER,
    department VARCHAR(50)
);

-- 創(chuàng)建 scores 表
CREATE TABLE scores (
    id SERIAL PRIMARY KEY,
    student_id INTEGER REFERENCES students(id),
    subject TEXT,
    score INTEGER
);

-- 插入示例數(shù)據(jù)
INSERT INTO students (name, age, department) VALUES
    ('Alice', 20, 'Computer Science'),
    ('Bob', 22, 'Mathematics');

INSERT INTO scores (student_id, subject, score) VALUES
    (1, 'Math', 90),
    (1, 'Science', 85),
    (2, 'Math', 88),
    (2, 'Science', 92);
  1. 接下來,我們創(chuàng)建一個(gè)ALSO型規(guī)則 add_scores,當(dāng)用戶查詢學(xué)生信息時(shí),同時(shí)返回學(xué)生的成績信息。
CREATE RULE add_scores AS
    ON SELECT TO students
    DO ALSO (
        SELECT scores.student_id, scores.subject, scores.score
        FROM scores
        WHERE scores.student_id = students.id
    );
  1. 現(xiàn)在,讓我們來測(cè)試這個(gè)規(guī)則。查詢學(xué)生信息時(shí),將同時(shí)返回學(xué)生的成績信息:
SELECT * FROM students;

-- 查詢結(jié)果:
-- id | name  | age |     department     | student_id | subject  | score
-- ----+-------+-----+--------------------+------------+----------+-------
--  1 | Alice |  20 | Computer Science   |          1 | Math     |    90
--  1 | Alice |  20 | Computer Science   |          1 | Science  |    85
--  2 | Bob   |  22 | Mathematics        |          2 | Math     |    88
--  2 | Bob   |  22 | Mathematics        |          2 | Science  |    92

??可以看到,使用ALSO型規(guī)則,我們成功地在查詢結(jié)果中同時(shí)返回了學(xué)生的成績信息,而不需要修改原始查詢語句。這樣可以方便地將多個(gè)查詢操作合并到一起,增強(qiáng)數(shù)據(jù)庫的查詢靈活性。

規(guī)則系統(tǒng)與觸發(fā)器的區(qū)別

??規(guī)則系統(tǒng)(Rule System)和觸發(fā)器(Trigger)是 PostgreSQL 中兩種不同的機(jī)制,用于在數(shù)據(jù)庫操作時(shí)實(shí)現(xiàn)額外的邏輯處理。雖然它們都可以用于在數(shù)據(jù)庫中定義和執(zhí)行額外的操作,但它們之間有一些重要的區(qū)別。

觸發(fā)器(Trigger):

  • 觸發(fā)器是與表相關(guān)聯(lián)的數(shù)據(jù)庫對(duì)象,它在特定的數(shù)據(jù)庫操作(如INSERT、UPDATE、DELETE)發(fā)生時(shí)觸發(fā)執(zhí)行。
  • 觸發(fā)器的執(zhí)行是自動(dòng)的,無需手動(dòng)調(diào)用,它們與特定的數(shù)據(jù)庫操作緊密綁定。
  • 觸發(fā)器可以在操作執(zhí)行之前(BEFORE)或之后(AFTER)觸發(fā),也可以定義為每個(gè)行(FOR EACH ROW)或每個(gè)語句(FOR EACH STATEMENT)觸發(fā)。
  • 觸發(fā)器的執(zhí)行可以修改正在進(jìn)行的操作,也可以對(duì)其他表執(zhí)行額外的操作,但觸發(fā)器不能返回結(jié)果集。
  • 觸發(fā)器通常用于實(shí)現(xiàn)數(shù)據(jù)完整性約束、日志記錄、審計(jì)跟蹤等業(yè)務(wù)邏輯。

規(guī)則系統(tǒng)(Rule System):

  • 規(guī)則系統(tǒng)是一個(gè)高級(jí)查詢重寫機(jī)制,它可以在查詢解析之后,在查詢執(zhí)行之前對(duì)查詢進(jìn)行修改和擴(kuò)展。
  • 規(guī)則系統(tǒng)通過使用 CREATE RULE 命令創(chuàng)建規(guī)則,允許在指定的事件上應(yīng)用規(guī)則,并對(duì)查詢進(jìn)行重寫。規(guī)則可以在 SELECT、INSERT、UPDATE 和 DELETE 等操作上應(yīng)用。
  • 規(guī)則的執(zhí)行是在查詢執(zhí)行之前,對(duì)查詢進(jìn)行預(yù)處理,根據(jù)規(guī)則定義的規(guī)則條件進(jìn)行查詢重寫。
  • 規(guī)則系統(tǒng)允許在原始查詢的基礎(chǔ)上添加附加的查詢操作,這些操作可以是 SELECT、INSERT、UPDATE 和 DELETE 等??梢允褂?DO ALSO 子句在規(guī)則中執(zhí)行其他查詢,并將其結(jié)果合并到最終查詢結(jié)果中。
  • 規(guī)則系統(tǒng)的主要用途是實(shí)現(xiàn)查詢優(yōu)化、添加額外的查詢操作、隱藏敏感數(shù)據(jù)等。

??觸發(fā)器和規(guī)則系統(tǒng)都提供了在數(shù)據(jù)庫操作時(shí)執(zhí)行額外邏輯的機(jī)制,但它們的應(yīng)用場(chǎng)景和實(shí)現(xiàn)方式不同。觸發(fā)器用于特定的數(shù)據(jù)庫操作,在操作前或操作后觸發(fā)執(zhí)行,而規(guī)則系統(tǒng)是對(duì)查詢進(jìn)行重寫的高級(jí)機(jī)制,允許在查詢執(zhí)行之前對(duì)查詢進(jìn)行修改和擴(kuò)展。觸發(fā)器通常用于數(shù)據(jù)完整性約束和日志記錄等場(chǎng)景,而規(guī)則系統(tǒng)主要用于查詢優(yōu)化和添加額外的查詢操作。

查詢重寫的處理操作

??查詢重寫部分的處理操作主要包括定義規(guī)則、刪除規(guī)則以及利用規(guī)則進(jìn)行查詢重寫。下面分別對(duì)這些操作進(jìn)行介紹。

定義重寫規(guī)則

??在 PostgreSQL 中,定義重寫規(guī)則是指創(chuàng)建規(guī)則以指定在查詢重寫過程中應(yīng)用的轉(zhuǎn)換規(guī)則。這些規(guī)則可以根據(jù)需要對(duì)查詢進(jìn)行修改、優(yōu)化或添加額外的邏輯。
??以下是定義重寫規(guī)則的一般步驟:

  1. 創(chuàng)建規(guī)則:
    使用 CREATE RULE 語句創(chuàng)建新的重寫規(guī)則。語法如下:
CREATE RULE rule_name AS ON event [ WHERE condition ]
    DO [ INSTEAD ] { query | command }
  • rule_name 是規(guī)則的名稱,可以根據(jù)需要命名。
  • event 指定了觸發(fā)規(guī)則的事件類型,如 SELECT、UPDATE、INSERT、DELETE 等。
  • condition 是一個(gè)可選的條件,用于指定規(guī)則應(yīng)用的條件。
  • query 或 command 是規(guī)則應(yīng)用的替換部分,可以是查詢語句或其他命令。
  1. 定義規(guī)則的替換部分:
    ??在 DO 關(guān)鍵字后,指定規(guī)則應(yīng)用時(shí)的替換部分。替換部分可以是查詢語句、修改語句或其他合法的 SQL 命令。
  2. 添加規(guī)則的條件:
    ??可以使用 WHERE 子句來定義規(guī)則應(yīng)用的條件。條件可以是一個(gè)布爾表達(dá)式,當(dāng)條件滿足時(shí)才會(huì)應(yīng)用規(guī)則。
  3. 選擇合適的事件類型:
    ??根據(jù)需求選擇合適的事件類型來觸發(fā)規(guī)則。例如,對(duì)于查詢重寫,可以選擇 SELECT 事件類型來匹配查詢語句。
  4. 理解規(guī)則的匹配順序:
    ??PostgreSQL 中的規(guī)則系統(tǒng)會(huì)按照特定的順序匹配規(guī)則并應(yīng)用它們。規(guī)則的匹配順序是按照規(guī)則創(chuàng)建的順序進(jìn)行的。因此,在定義多個(gè)規(guī)則時(shí),確保規(guī)則的順序正確,以便按照預(yù)期應(yīng)用規(guī)則。

??由上述可知,在使用規(guī)則系統(tǒng)之前首先需要定義規(guī)則,規(guī)則的定義通過CREATE RULE命令來完成。而定義重寫規(guī)則的操作主要由函數(shù)DefineRule實(shí)現(xiàn),“CREATE RUILE”命令被詞法和語法分析模塊處理之后,相關(guān)信息被存儲(chǔ)在一個(gè)RuleStmt 結(jié)構(gòu)中,最后查詢執(zhí)行模塊會(huì)把該結(jié)構(gòu)交給DefineRule來完成規(guī)則的創(chuàng)建。

??DefineRule的源碼如下:(路徑:src/backend/rewrite/rewriteDefine.c

/*
 * DefineRule
 *		Execute a CREATE RULE command.
 */
ObjectAddress
DefineRule(RuleStmt *stmt, const char *queryString)
{
	List	   *actions;
	Node	   *whereClause;
	Oid			relId;

	/* Parse analysis. */
	/* 首先調(diào)用transformRuleStmt對(duì)RuleStmt結(jié)構(gòu)體進(jìn)行處理 */
	transformRuleStmt(stmt, queryString, &actions, &whereClause);

	/*
	 * Find and lock the relation.  Lock level should match
	 * DefineQueryRewrite.
	 * RangeVarGetRelid函數(shù)通過調(diào)用RangeVarGetRelidExtended函數(shù)選擇正確的命名空間并找到表的OID
	 */
	relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);

	/* ... and execute 
	 * 調(diào)用DefineQueryRewrite函數(shù),
	 * 將已經(jīng)處理好的規(guī)則,作為一個(gè)元組,插入到系統(tǒng)表pg_rewrite中,
	 * DefineQueryRewrite會(huì)把處理好的where子句的表達(dá)式樹以及規(guī)則的動(dòng)作作為其參數(shù)之一
	*/
	return DefineQueryRewrite(stmt->rulename,
							  relId,
							  whereClause,
							  stmt->event,
							  stmt->instead,
							  stmt->replace,
							  actions);
}

??RuleStmt結(jié)構(gòu)如下:(路徑:src/include/nodes/parsenodes.h)

/* ----------------------
 *		Create Rule Statement
 * ----------------------
 */
typedef struct RuleStmt
{
	NodeTag		type;
	RangeVar   *relation;		/* relation the rule is for */
	char	   *rulename;		/* name of the rule */
	Node	   *whereClause;	/* qualifications */
	CmdType		event;			/* SELECT, INSERT, etc */
	bool		instead;		/* is a 'do instead'? */
	List	   *actions;		/* the action statements */
	bool		replace;		/* OR REPLACE */
} RuleStmt;

【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫

??DefineRule的流程如下:

  1. 首先調(diào)用transformRuleStmt對(duì)RuleStmt進(jìn)行處理:
    ??在 PostgreSQL 中,transformRuleStmt 函數(shù)是用于將 RuleStmt 結(jié)構(gòu)轉(zhuǎn)換為 RuleData 結(jié)構(gòu)的主要函數(shù)。它的作用是將用戶定義的規(guī)則語句進(jìn)行語義分析和轉(zhuǎn)換,生成相應(yīng)的規(guī)則對(duì)象,用于后續(xù)的查詢重寫和觸發(fā)器處理。

以下是 transformRuleStmt 函數(shù)的主要處理步驟:

  • 語義分析:首先,transformRuleStmt 函數(shù)會(huì)對(duì) RuleStmt 結(jié)構(gòu)進(jìn)行語義分析,檢查規(guī)則的合法性和正確性。它會(huì)驗(yàn)證規(guī)則名是否已經(jīng)存在、表是否存在、規(guī)則的定義語句是否有效等。
  • 解析規(guī)則定義:接著,函數(shù)會(huì)解析 RuleStmt 結(jié)構(gòu),提取其中的信息,例如規(guī)則名、表名、觸發(fā)事件類型、規(guī)則的定義語句等。
  • 獲取觸發(fā)函數(shù)信息:對(duì)于 RuleStmt 中的 actions,即規(guī)則的定義語句,函數(shù)會(huì)解析其中的觸發(fā)函數(shù)信息,并進(jìn)行一系列驗(yàn)證,例如函數(shù)是否存在、函數(shù)的參數(shù)是否匹配等。
  • 創(chuàng)建規(guī)則對(duì)象:經(jīng)過語義分析和解析后,transformRuleStmt 函數(shù)會(huì)創(chuàng)建一個(gè)新的 RuleData 結(jié)構(gòu),用于表示要添加的規(guī)則。這個(gè)結(jié)構(gòu)中包含了規(guī)則的詳細(xì)信息,如規(guī)則名、表名、觸發(fā)事件類型、觸發(fā)函數(shù)等。
  • 完成處理:最后,transformRuleStmt 函數(shù)會(huì)返回創(chuàng)建的 RuleData 結(jié)構(gòu),將它作為規(guī)則定義的結(jié)果。這個(gè) RuleData 結(jié)構(gòu)可以用于后續(xù)的處理,例如添加到全局規(guī)則列表 RuleRelation 中,或?qū)懭胂到y(tǒng)表 pg_rewrite,從而將規(guī)則持久化到數(shù)據(jù)庫中。

??DefineRule調(diào)用的transformRuleStmt函數(shù)的源碼如下:

/*
 * transformRuleStmt -
 *	  transform a CREATE RULE Statement. The action is a list of parse
 *	  trees which is transformed into a list of query trees, and we also
 *	  transform the WHERE clause if any.
 *
 * actions and whereClause are output parameters that receive the
 * transformed results.
 *
 * Note that we must not scribble on the passed-in RuleStmt, so we do
 * copyObject() on the actions and WHERE clause.
 */
void
transformRuleStmt(RuleStmt *stmt, const char *queryString,
				  List **actions, Node **whereClause)
{
	Relation	rel;
	ParseState *pstate;
	RangeTblEntry *oldrte;
	RangeTblEntry *newrte;

	/*
	 * To avoid deadlock, make sure the first thing we do is grab
	 * AccessExclusiveLock on the target relation.  This will be needed by
	 * DefineQueryRewrite(), and we don't want to grab a lesser lock
	 * beforehand.
	 */
	rel = heap_openrv(stmt->relation, AccessExclusiveLock);

	if (rel->rd_rel->relkind == RELKIND_MATVIEW)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("rules on materialized views are not supported")));

	/* Set up pstate */
	pstate = make_parsestate(NULL);
	pstate->p_sourcetext = queryString;

	/*
	 * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
	 * Set up their RTEs in the main pstate for use in parsing the rule
	 * qualification.
	 */
	oldrte = addRangeTableEntryForRelation(pstate, rel,
										   makeAlias("old", NIL),
										   false, false);
	newrte = addRangeTableEntryForRelation(pstate, rel,
										   makeAlias("new", NIL),
										   false, false);
	/* Must override addRangeTableEntry's default access-check flags */
	oldrte->requiredPerms = 0;
	newrte->requiredPerms = 0;

	/*
	 * They must be in the namespace too for lookup purposes, but only add the
	 * one(s) that are relevant for the current kind of rule.  In an UPDATE
	 * rule, quals must refer to OLD.field or NEW.field to be unambiguous, but
	 * there's no need to be so picky for INSERT & DELETE.  We do not add them
	 * to the joinlist.
	 */
	switch (stmt->event)
	{
		case CMD_SELECT:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			break;
		case CMD_UPDATE:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			addRTEtoQuery(pstate, newrte, false, true, true);
			break;
		case CMD_INSERT:
			addRTEtoQuery(pstate, newrte, false, true, true);
			break;
		case CMD_DELETE:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			break;
		default:
			elog(ERROR, "unrecognized event type: %d",
				 (int) stmt->event);
			break;
	}

	/* take care of the where clause */
	*whereClause = transformWhereClause(pstate,
										(Node *) copyObject(stmt->whereClause),
										EXPR_KIND_WHERE,
										"WHERE");
	/* we have to fix its collations too */
	assign_expr_collations(pstate, *whereClause);

	/* this is probably dead code without add_missing_from: */
	if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("rule WHERE condition cannot contain references to other relations")));

	/*
	 * 'instead nothing' rules with a qualification need a query rangetable so
	 * the rewrite handler can add the negated rule qualification to the
	 * original query. We create a query with the new command type CMD_NOTHING
	 * here that is treated specially by the rewrite system.
	 */
	if (stmt->actions == NIL)
	{
		Query	   *nothing_qry = makeNode(Query);

		nothing_qry->commandType = CMD_NOTHING;
		nothing_qry->rtable = pstate->p_rtable;
		nothing_qry->jointree = makeFromExpr(NIL, NULL);	/* no join wanted */

		*actions = list_make1(nothing_qry);
	}
	else
	{
		ListCell   *l;
		List	   *newactions = NIL;

		/*
		 * transform each statement, like parse_sub_analyze()
		 */
		foreach(l, stmt->actions)
		{
			Node	   *action = (Node *) lfirst(l);
			ParseState *sub_pstate = make_parsestate(NULL);
			Query	   *sub_qry,
					   *top_subqry;
			bool		has_old,
						has_new;

			/*
			 * Since outer ParseState isn't parent of inner, have to pass down
			 * the query text by hand.
			 */
			sub_pstate->p_sourcetext = queryString;

			/*
			 * Set up OLD/NEW in the rtable for this statement.  The entries
			 * are added only to relnamespace, not varnamespace, because we
			 * don't want them to be referred to by unqualified field names
			 * nor "*" in the rule actions.  We decide later whether to put
			 * them in the joinlist.
			 */
			oldrte = addRangeTableEntryForRelation(sub_pstate, rel,
												   makeAlias("old", NIL),
												   false, false);
			newrte = addRangeTableEntryForRelation(sub_pstate, rel,
												   makeAlias("new", NIL),
												   false, false);
			oldrte->requiredPerms = 0;
			newrte->requiredPerms = 0;
			addRTEtoQuery(sub_pstate, oldrte, false, true, false);
			addRTEtoQuery(sub_pstate, newrte, false, true, false);

			/* Transform the rule action statement */
			top_subqry = transformStmt(sub_pstate,
									   (Node *) copyObject(action));

			/*
			 * We cannot support utility-statement actions (eg NOTIFY) with
			 * nonempty rule WHERE conditions, because there's no way to make
			 * the utility action execute conditionally.
			 */
			if (top_subqry->commandType == CMD_UTILITY &&
				*whereClause != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));

			/*
			 * If the action is INSERT...SELECT, OLD/NEW have been pushed down
			 * into the SELECT, and that's what we need to look at. (Ugly
			 * kluge ... try to fix this when we redesign querytrees.)
			 */
			sub_qry = getInsertSelectQuery(top_subqry, NULL);

			/*
			 * If the sub_qry is a setop, we cannot attach any qualifications
			 * to it, because the planner won't notice them.  This could
			 * perhaps be relaxed someday, but for now, we may as well reject
			 * such a rule immediately.
			 */
			if (sub_qry->setOperations != NULL && *whereClause != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));

			/*
			 * Validate action's use of OLD/NEW, qual too
			 */
			has_old =
				rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
				rangeTableEntry_used(*whereClause, PRS2_OLD_VARNO, 0);
			has_new =
				rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
				rangeTableEntry_used(*whereClause, PRS2_NEW_VARNO, 0);

			switch (stmt->event)
			{
				case CMD_SELECT:
					if (has_old)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON SELECT rule cannot use OLD")));
					if (has_new)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON SELECT rule cannot use NEW")));
					break;
				case CMD_UPDATE:
					/* both are OK */
					break;
				case CMD_INSERT:
					if (has_old)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON INSERT rule cannot use OLD")));
					break;
				case CMD_DELETE:
					if (has_new)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("ON DELETE rule cannot use NEW")));
					break;
				default:
					elog(ERROR, "unrecognized event type: %d",
						 (int) stmt->event);
					break;
			}

			/*
			 * OLD/NEW are not allowed in WITH queries, because they would
			 * amount to outer references for the WITH, which we disallow.
			 * However, they were already in the outer rangetable when we
			 * analyzed the query, so we have to check.
			 *
			 * Note that in the INSERT...SELECT case, we need to examine the
			 * CTE lists of both top_subqry and sub_qry.
			 *
			 * Note that we aren't digging into the body of the query looking
			 * for WITHs in nested sub-SELECTs.  A WITH down there can
			 * legitimately refer to OLD/NEW, because it'd be an
			 * indirect-correlated outer reference.
			 */
			if (rangeTableEntry_used((Node *) top_subqry->cteList,
									 PRS2_OLD_VARNO, 0) ||
				rangeTableEntry_used((Node *) sub_qry->cteList,
									 PRS2_OLD_VARNO, 0))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot refer to OLD within WITH query")));
			if (rangeTableEntry_used((Node *) top_subqry->cteList,
									 PRS2_NEW_VARNO, 0) ||
				rangeTableEntry_used((Node *) sub_qry->cteList,
									 PRS2_NEW_VARNO, 0))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot refer to NEW within WITH query")));

			/*
			 * For efficiency's sake, add OLD to the rule action's jointree
			 * only if it was actually referenced in the statement or qual.
			 *
			 * For INSERT, NEW is not really a relation (only a reference to
			 * the to-be-inserted tuple) and should never be added to the
			 * jointree.
			 *
			 * For UPDATE, we treat NEW as being another kind of reference to
			 * OLD, because it represents references to *transformed* tuples
			 * of the existing relation.  It would be wrong to enter NEW
			 * separately in the jointree, since that would cause a double
			 * join of the updated relation.  It's also wrong to fail to make
			 * a jointree entry if only NEW and not OLD is mentioned.
			 */
			if (has_old || (has_new && stmt->event == CMD_UPDATE))
			{
				/*
				 * If sub_qry is a setop, manipulating its jointree will do no
				 * good at all, because the jointree is dummy. (This should be
				 * a can't-happen case because of prior tests.)
				 */
				if (sub_qry->setOperations != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
				/* hack so we can use addRTEtoQuery() */
				sub_pstate->p_rtable = sub_qry->rtable;
				sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
				addRTEtoQuery(sub_pstate, oldrte, true, false, false);
				sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
			}

			newactions = lappend(newactions, top_subqry);

			free_parsestate(sub_pstate);
		}

		*actions = newactions;
	}

	free_parsestate(pstate);

	/* Close relation, but keep the exclusive lock */
	heap_close(rel, NoLock);
}

  1. 獲取要定義規(guī)則的表的OID。
  2. 調(diào)用函數(shù)DefineQueryRewrite將己處理好的規(guī)則作為一個(gè)元組插人到系統(tǒng)表pg_rewrite中,DefineQueryRewrite 會(huì)把處理好的WHERE子句的表達(dá)式樹以及規(guī)則的動(dòng)作作為其參數(shù)之一。

??DefineQueryRewrite函數(shù)調(diào)用的InsertRule函數(shù)源碼如下:路徑(src/backend/rewrite/rewriteDefine.c

/*
 * DefineQueryRewrite
 *		Create a rule
 *
 * This is essentially the same as DefineRule() except that the rule's
 * action and qual have already been passed through parse analysis.
 */
ObjectAddress
DefineQueryRewrite(char *rulename,
				   Oid event_relid,
				   Node *event_qual,
				   CmdType event_type,
				   bool is_instead,
				   bool replace,
				   List *action)
{
	Relation	event_relation;
	ListCell   *l;
	Query	   *query;
	bool		RelisBecomingView = false;
	Oid			ruleId = InvalidOid;
	ObjectAddress address;

	/*
	 * If we are installing an ON SELECT rule, we had better grab
	 * AccessExclusiveLock to ensure no SELECTs are currently running on the
	 * event relation. For other types of rules, it would be sufficient to
	 * grab ShareRowExclusiveLock to lock out insert/update/delete actions and
	 * to ensure that we lock out current CREATE RULE statements; but because
	 * of race conditions in access to catalog entries, we can't do that yet.
	 *
	 * Note that this lock level should match the one used in DefineRule.
	 */
	event_relation = heap_open(event_relid, AccessExclusiveLock);

	/*
	 * Verify relation is of a type that rules can sensibly be applied to.
	 * Internal callers can target materialized views, but transformRuleStmt()
	 * blocks them for users.  Don't mention them in the error message.
	 */
	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
		event_relation->rd_rel->relkind != RELKIND_VIEW &&
		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a table or view",
						RelationGetRelationName(event_relation))));

	if (!allowSystemTableMods && IsSystemRelation(event_relation))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(event_relation))));

	/*
	 * Check user has permission to apply rules to this relation.
	 */
	if (!pg_class_ownercheck(event_relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
					   RelationGetRelationName(event_relation));

	/*
	 * No rule actions that modify OLD or NEW
	 */
	foreach(l, action)
	{
		query = lfirst_node(Query, l);
		if (query->resultRelation == 0)
			continue;
		/* Don't be fooled by INSERT/SELECT */
		if (query != getInsertSelectQuery(query, NULL))
			continue;
		if (query->resultRelation == PRS2_OLD_VARNO)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rule actions on OLD are not implemented"),
					 errhint("Use views or triggers instead.")));
		if (query->resultRelation == PRS2_NEW_VARNO)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rule actions on NEW are not implemented"),
					 errhint("Use triggers instead.")));
	}

	if (event_type == CMD_SELECT)
	{
		/*
		 * Rules ON SELECT are restricted to view definitions
		 *
		 * So there cannot be INSTEAD NOTHING, ...
		 */
		if (list_length(action) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
					 errhint("Use views instead.")));

		/*
		 * ... there cannot be multiple actions, ...
		 */
		if (list_length(action) > 1)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("multiple actions for rules on SELECT are not implemented")));

		/*
		 * ... the one action must be a SELECT, ...
		 */
		query = linitial_node(Query, action);
		if (!is_instead ||
			query->commandType != CMD_SELECT)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rules on SELECT must have action INSTEAD SELECT")));

		/*
		 * ... it cannot contain data-modifying WITH ...
		 */
		if (query->hasModifyingCTE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rules on SELECT must not contain data-modifying statements in WITH")));

		/*
		 * ... there can be no rule qual, ...
		 */
		if (event_qual != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("event qualifications are not implemented for rules on SELECT")));

		/*
		 * ... the targetlist of the SELECT action must exactly match the
		 * event relation, ...
		 */
		checkRuleResultList(query->targetList,
							RelationGetDescr(event_relation),
							true,
							event_relation->rd_rel->relkind !=
							RELKIND_MATVIEW);

		/*
		 * ... there must not be another ON SELECT rule already ...
		 */
		if (!replace && event_relation->rd_rules != NULL)
		{
			int			i;

			for (i = 0; i < event_relation->rd_rules->numLocks; i++)
			{
				RewriteRule *rule;

				rule = event_relation->rd_rules->rules[i];
				if (rule->event == CMD_SELECT)
					ereport(ERROR,
							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
							 errmsg("\"%s\" is already a view",
									RelationGetRelationName(event_relation))));
			}
		}

		/*
		 * ... and finally the rule must be named _RETURN.
		 */
		if (strcmp(rulename, ViewSelectRuleName) != 0)
		{
			/*
			 * In versions before 7.3, the expected name was _RETviewname. For
			 * backwards compatibility with old pg_dump output, accept that
			 * and silently change it to _RETURN.  Since this is just a quick
			 * backwards-compatibility hack, limit the number of characters
			 * checked to a few less than NAMEDATALEN; this saves having to
			 * worry about where a multibyte character might have gotten
			 * truncated.
			 */
			if (strncmp(rulename, "_RET", 4) != 0 ||
				strncmp(rulename + 4, RelationGetRelationName(event_relation),
						NAMEDATALEN - 4 - 4) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("view rule for \"%s\" must be named \"%s\"",
								RelationGetRelationName(event_relation),
								ViewSelectRuleName)));
			rulename = pstrdup(ViewSelectRuleName);
		}

		/*
		 * Are we converting a relation to a view?
		 *
		 * If so, check that the relation is empty because the storage for the
		 * relation is going to be deleted.  Also insist that the rel not have
		 * any triggers, indexes, child tables, policies, or RLS enabled.
		 * (Note: these tests are too strict, because they will reject
		 * relations that once had such but don't anymore.  But we don't
		 * really care, because this whole business of converting relations to
		 * views is just a kluge to allow dump/reload of views that
		 * participate in circular dependencies.)
		 */
		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
		{
			HeapScanDesc scanDesc;
			Snapshot	snapshot;

			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot convert partitioned table \"%s\" to a view",
								RelationGetRelationName(event_relation))));

			if (event_relation->rd_rel->relispartition)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot convert partition \"%s\" to a view",
								RelationGetRelationName(event_relation))));

			snapshot = RegisterSnapshot(GetLatestSnapshot());
			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it is not empty",
								RelationGetRelationName(event_relation))));
			heap_endscan(scanDesc);
			UnregisterSnapshot(snapshot);

			if (event_relation->rd_rel->relhastriggers)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has triggers",
								RelationGetRelationName(event_relation)),
						 errhint("In particular, the table cannot be involved in any foreign key relationships.")));

			if (event_relation->rd_rel->relhasindex)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has indexes",
								RelationGetRelationName(event_relation))));

			if (event_relation->rd_rel->relhassubclass)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has child tables",
								RelationGetRelationName(event_relation))));

			if (event_relation->rd_rel->relrowsecurity)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has row security enabled",
								RelationGetRelationName(event_relation))));

			if (relation_has_policies(event_relation))
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has row security policies",
								RelationGetRelationName(event_relation))));

			RelisBecomingView = true;
		}
	}
	else
	{
		/*
		 * For non-SELECT rules, a RETURNING list can appear in at most one of
		 * the actions ... and there can't be any RETURNING list at all in a
		 * conditional or non-INSTEAD rule.  (Actually, there can be at most
		 * one RETURNING list across all rules on the same event, but it seems
		 * best to enforce that at rule expansion time.)  If there is a
		 * RETURNING list, it must match the event relation.
		 */
		bool		haveReturning = false;

		foreach(l, action)
		{
			query = lfirst_node(Query, l);

			if (!query->returningList)
				continue;
			if (haveReturning)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot have multiple RETURNING lists in a rule")));
			haveReturning = true;
			if (event_qual != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in conditional rules")));
			if (!is_instead)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
			checkRuleResultList(query->returningList,
								RelationGetDescr(event_relation),
								false, false);
		}
	}

	/*
	 * This rule is allowed - prepare to install it.
	 */

	/* discard rule if it's null action and not INSTEAD; it's a no-op */
	if (action != NIL || is_instead)
	{
		ruleId = InsertRule(rulename,
							event_type,
							event_relid,
							is_instead,
							event_qual,
							action,
							replace);

		/*
		 * Set pg_class 'relhasrules' field TRUE for event relation.
		 *
		 * Important side effect: an SI notice is broadcast to force all
		 * backends (including me!) to update relcache entries with the new
		 * rule.
		 */
		SetRelationRuleStatus(event_relid, true);
	}

	/* ---------------------------------------------------------------------
	 * If the relation is becoming a view:
	 * - delete the associated storage files
	 * - get rid of any system attributes in pg_attribute; a view shouldn't
	 *	 have any of those
	 * - remove the toast table; there is no need for it anymore, and its
	 *	 presence would make vacuum slightly more complicated
	 * - set relkind to RELKIND_VIEW, and adjust other pg_class fields
	 *	 to be appropriate for a view
	 *
	 * NB: we had better have AccessExclusiveLock to do this ...
	 * ---------------------------------------------------------------------
	 */
	if (RelisBecomingView)
	{
		Relation	relationRelation;
		Oid			toastrelid;
		HeapTuple	classTup;
		Form_pg_class classForm;

		relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
		toastrelid = event_relation->rd_rel->reltoastrelid;

		/* drop storage while table still looks like a table  */
		RelationDropStorage(event_relation);
		DeleteSystemAttributeTuples(event_relid);

		/*
		 * Drop the toast table if any.  (This won't take care of updating the
		 * toast fields in the relation's own pg_class entry; we handle that
		 * below.)
		 */
		if (OidIsValid(toastrelid))
		{
			ObjectAddress toastobject;

			/*
			 * Delete the dependency of the toast relation on the main
			 * relation so we can drop the former without dropping the latter.
			 */
			deleteDependencyRecordsFor(RelationRelationId, toastrelid,
									   false);

			/* Make deletion of dependency record visible */
			CommandCounterIncrement();

			/* Now drop toast table, including its index */
			toastobject.classId = RelationRelationId;
			toastobject.objectId = toastrelid;
			toastobject.objectSubId = 0;
			performDeletion(&toastobject, DROP_RESTRICT,
							PERFORM_DELETION_INTERNAL);
		}

		/*
		 * SetRelationRuleStatus may have updated the pg_class row, so we must
		 * advance the command counter before trying to update it again.
		 */
		CommandCounterIncrement();

		/*
		 * Fix pg_class entry to look like a normal view's, including setting
		 * the correct relkind and removal of reltoastrelid of the toast table
		 * we potentially removed above.
		 */
		classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
		if (!HeapTupleIsValid(classTup))
			elog(ERROR, "cache lookup failed for relation %u", event_relid);
		classForm = (Form_pg_class) GETSTRUCT(classTup);

		classForm->reltablespace = InvalidOid;
		classForm->relpages = 0;
		classForm->reltuples = 0;
		classForm->relallvisible = 0;
		classForm->reltoastrelid = InvalidOid;
		classForm->relhasindex = false;
		classForm->relkind = RELKIND_VIEW;
		classForm->relhasoids = false;
		classForm->relhaspkey = false;
		classForm->relfrozenxid = InvalidTransactionId;
		classForm->relminmxid = InvalidMultiXactId;
		classForm->relreplident = REPLICA_IDENTITY_NOTHING;

		CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup);

		heap_freetuple(classTup);
		heap_close(relationRelation, RowExclusiveLock);
	}

	ObjectAddressSet(address, RewriteRelationId, ruleId);

	/* Close rel, but keep lock till commit... */
	heap_close(event_relation, NoLock);

	return address;
}

??函數(shù) DefineQueryRewrite 的流程如圖5-19 所示:
【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫

刪除重寫規(guī)則

??刪除重寫規(guī)則是指從數(shù)據(jù)庫中刪除一個(gè)已經(jīng)存在的規(guī)則。重寫規(guī)則是一種特殊類型的規(guī)則,它允許在執(zhí)行查詢或修改數(shù)據(jù)庫時(shí),自動(dòng)將查詢重寫為另一個(gè)查詢。
??在PostgresSQL中實(shí)現(xiàn)了兩種刪除規(guī)則的方式:第一種是根據(jù)規(guī)則名刪除規(guī)則,由函數(shù)RemoveRewriteRule實(shí)現(xiàn);第二種是根據(jù)規(guī)則的OID刪除規(guī)則,由函數(shù)RemoveRewriteRuleById實(shí)現(xiàn)。當(dāng)我們輸入“DROP RULE”命令刪除規(guī)則時(shí),實(shí)際的刪除工作將由RemoveRewriteRule完成;而RemoveRewriteRuleByld的作用是在刪除其他對(duì)象(比如視圖)時(shí),用于級(jí)聯(lián)刪除與這個(gè)對(duì)象相關(guān)的規(guī)則。
??RemoveRewriteRule函數(shù)的主要流程如圖5-20所示。
【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫

對(duì)查詢樹進(jìn)行重寫

??Pg_rewrite_query中會(huì)調(diào)用函數(shù)QueryRewrite來完成查詢樹的重寫。重寫規(guī)則定義了在查詢執(zhí)行過程中,將一個(gè)查詢樹轉(zhuǎn)換成另一個(gè)查詢樹的規(guī)則。這些規(guī)則存儲(chǔ)在系統(tǒng)表 pg_rewrite 中。
??QueryRewrite 函數(shù)的流程如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-583747.html

  1. 獲取要執(zhí)行的規(guī)則列表:
    首先,QueryRewrite 函數(shù)會(huì)根據(jù)查詢樹中的關(guān)聯(lián)表和查詢類型(SELECT、INSERT、UPDATE、DELETE等)來獲取與該表相關(guān)的重寫規(guī)則。它會(huì)查詢系統(tǒng)表 pg_rewrite 來找到適用于當(dāng)前查詢的規(guī)則列表。
  2. 根據(jù)規(guī)則排序:
    獲取到的規(guī)則列表會(huì)按照 ev_priority 字段進(jìn)行排序,優(yōu)先級(jí)越高的規(guī)則排在前面。如果有多個(gè)規(guī)則適用于當(dāng)前查詢,按照優(yōu)先級(jí)從高到低的順序依次應(yīng)用規(guī)則。
  3. 檢查規(guī)則條件:
    對(duì)于每個(gè)規(guī)則,QueryRewrite 函數(shù)會(huì)檢查規(guī)則的 ev_qual 字段,即規(guī)則的條件表達(dá)式。如果規(guī)則定義了條件,并且條件不滿足(即返回值為 false),則該規(guī)則不會(huì)被應(yīng)用于當(dāng)前查詢,繼續(xù)嘗試下一個(gè)規(guī)則。
  4. 應(yīng)用規(guī)則:
    如果規(guī)則的條件滿足,QueryRewrite 函數(shù)將應(yīng)用規(guī)則中定義的查詢樹轉(zhuǎn)換操作。這通常涉及替換查詢樹的部分或全部節(jié)點(diǎn),以便得到一個(gè)新的查詢樹。應(yīng)用規(guī)則后,函數(shù)將繼續(xù)檢查下一個(gè)規(guī)則。
  5. 合并規(guī)則結(jié)果:
    重寫過程中可能會(huì)應(yīng)用多個(gè)規(guī)則,每個(gè)規(guī)則可能會(huì)生成一個(gè)新的查詢樹。QueryRewrite 函數(shù)會(huì)合并所有應(yīng)用過的規(guī)則的結(jié)果,以確保最終得到一個(gè)最終的查詢樹。
  6. 完成重寫:
    一旦所有適用的規(guī)則都應(yīng)用完畢,QueryRewrite 函數(shù)將返回重寫后的最終查詢樹。
    【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】,PostgerSQL,postgresql,學(xué)習(xí),數(shù)據(jù)庫

到了這里,關(guān)于【PostgreSQL內(nèi)核學(xué)習(xí)(三)—— 查詢重寫】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【PostgreSQL內(nèi)核學(xué)習(xí)(一)—— Ubuntu源碼安裝PostgreSQL】

    【PostgreSQL內(nèi)核學(xué)習(xí)(一)—— Ubuntu源碼安裝PostgreSQL】

    下載地址:https://www.postgresql.org/ftp/source/v10.1/ 執(zhí)行命令: 解壓成功后顯示: 出現(xiàn)問題: 解決方法:執(zhí)行以下命令。 執(zhí)行命令: 注意:如果希望后續(xù)在gdb時(shí)可以查看代碼,則需要添加–enable-debug make時(shí)出現(xiàn)錯(cuò)誤: 解決方法:找到 copy_fetch.c 文件。 文件路徑如下: /home/jia/pg

    2024年02月16日
    瀏覽(47)
  • 【PostgreSQL內(nèi)核學(xué)習(xí)(十三)—— (PortalRun)】

    聲明 :本文的部分內(nèi)容參考了他人的文章。在編寫過程中,我們尊重他人的知識(shí)產(chǎn)權(quán)和學(xué)術(shù)成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。 本文主要參考了《PostgresSQL數(shù)據(jù)庫內(nèi)核分析》一書,OpenGauss1.1.0 的開源代碼和《OpenGauss數(shù)據(jù)庫源碼解析》一書以及Op

    2024年02月07日
    瀏覽(17)
  • 【數(shù)據(jù)庫】查詢PostgreSQL中所有表邏輯外鍵

    在PostgreSQL數(shù)據(jù)庫中,邏輯外鍵是用于約束表之間關(guān)系的一種機(jī)制。然而,在某些情況下,我們可能需要?jiǎng)h除和重建邏輯外鍵。本文將介紹如何查詢PostgreSQL中所有表的邏輯外鍵,并指導(dǎo)您如何先刪除再重新建立這些外鍵。 要查詢PostgreSQL中所有表的邏輯外鍵,您可以使用以下

    2024年02月10日
    瀏覽(32)
  • 【PostgreSQL內(nèi)核學(xué)習(xí)(二十三)—— 執(zhí)行器(ExecEndPlan)】

    【PostgreSQL內(nèi)核學(xué)習(xí)(二十三)—— 執(zhí)行器(ExecEndPlan)】

    聲明 :本文的部分內(nèi)容參考了他人的文章。在編寫過程中,我們尊重他人的知識(shí)產(chǎn)權(quán)和學(xué)術(shù)成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。 本文主要參考了 postgresql-10.1 的開源代碼和《OpenGauss數(shù)據(jù)庫源碼解析》和《PostgresSQL數(shù)據(jù)庫內(nèi)核分析》一書 ??在這三

    2024年01月17日
    瀏覽(30)
  • 【PostgreSQL內(nèi)核學(xué)習(xí)(二十一)—— 執(zhí)行器(InitPlan)】

    【PostgreSQL內(nèi)核學(xué)習(xí)(二十一)—— 執(zhí)行器(InitPlan)】

    聲明 :本文的部分內(nèi)容參考了他人的文章。在編寫過程中,我們尊重他人的知識(shí)產(chǎn)權(quán)和學(xué)術(shù)成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。 本文主要參考了 postgresql-10.1 的開源代碼和《OpenGauss數(shù)據(jù)庫源碼解析》和《PostgresSQL數(shù)據(jù)庫內(nèi)核分析》一書 ??在【

    2024年01月16日
    瀏覽(24)
  • 【PostgreSQL內(nèi)核學(xué)習(xí)(二十四) —— (ALTER MATERIALIZED VIEW)】

    聲明 :本文的部分內(nèi)容參考了他人的文章。在編寫過程中,我們尊重他人的知識(shí)產(chǎn)權(quán)和學(xué)術(shù)成果,力求遵循合理使用原則,并在適用的情況下注明引用來源。 本文主要參考了 postgresql-10.1 的開源代碼和《OpenGauss數(shù)據(jù)庫源碼解析》和《PostgresSQL數(shù)據(jù)庫內(nèi)核分析》一書以及一些相

    2024年01月18日
    瀏覽(20)
  • PostgreSQL數(shù)據(jù)庫以任意時(shí)間間隔聚合查詢group by

    PostgreSQL數(shù)據(jù)庫以任意時(shí)間間隔聚合查詢group by

    我們做的是智慧交通信控平臺(tái),需要將實(shí)時(shí)采集到的交通大數(shù)據(jù)(信號(hào)機(jī)燈態(tài)、卡口過車、雷達(dá)數(shù)據(jù)等)全部入庫,按照時(shí)間順序存儲(chǔ) 然后根據(jù)原始數(shù)據(jù),再計(jì)算出一些交通評(píng)價(jià)指標(biāo),存儲(chǔ)到數(shù)據(jù)庫,供后續(xù)聚合查詢和分析統(tǒng)計(jì) 前端設(shè)備(信號(hào)機(jī)、雷達(dá)、卡口等)上報(bào)原始

    2024年02月02日
    瀏覽(94)
  • MyBatis-Plus 查詢PostgreSQL數(shù)據(jù)庫jsonb類型保持原格式

    MyBatis-Plus 查詢PostgreSQL數(shù)據(jù)庫jsonb類型保持原格式

    在這篇文章,我們保存了數(shù)據(jù)庫的jsonb類型:MyBatis-Plus 實(shí)現(xiàn)PostgreSQL數(shù)據(jù)庫jsonb類型的保存與查詢 這篇文章介紹了模糊查詢json/jsonb類型:PostgreSQL 查詢json/jsonb是否存在某個(gè)片段 在模糊查詢json/jsonb之前,我們得拿到正確的json/jsonb片段,比如我們模糊查詢好幾個(gè)連著的鍵值對(duì),

    2024年02月15日
    瀏覽(110)
  • 【數(shù)據(jù)庫】PostgreSQL中使用`SELECT DISTINCT`和`SUBSTRING`函數(shù)實(shí)現(xiàn)去重查詢

    在PostgreSQL中,我們可以使用 SELECT DISTINCT 和 SUBSTRING 函數(shù)來實(shí)現(xiàn)對(duì)某個(gè)字段進(jìn)行去重查詢。本文將介紹如何使用這兩個(gè)函數(shù)來實(shí)現(xiàn)對(duì) resource_version 字段的去重查詢。 1. SELECT DISTINCT 語句 SELECT DISTINCT 語句用于從表中選擇不重復(fù)的記錄。如果沒有指定列名,則會(huì)選擇所有列。在本

    2024年02月14日
    瀏覽(30)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包