簡體   English   中英

PostgreSQL - ON CONFLICT UPDATE with view with columns子集

[英]PostgreSQL - ON CONFLICT UPDATE with view with subset of columns

目前正在運行9.5.3版。 當然是更新計划。

我有一個PostgreSQL數據庫,其架構早於表行級安全性(即CREATE POLICY ... )。 使用視圖實現行級安全性。 通過僅選擇具有與CURRENT_USER匹配的所有者名稱的行,在視圖中完成安全性。

我正在嘗試使用這樣的視圖構建一個upsert查詢。 當我嘗試命名conflict_target

使用ON CONFLICT UPDATE ...來自於命名違反了什么約束。

玩具示例

CREATE TABLE foo (id serial, num int, word text, data text, ownername varchar(64));

對於每個用戶, wordnum的組合必須是唯一的。

CREATE UNIQUE INDEX foo_num_word_owner_idx ON foo (num, word, ownername);

使用基於當前用戶名的視圖實現行級安全性。 為視圖授予權限,為普通用戶刪除基礎表。 在v 9.5之后添加了security_barrier 請注意,用戶看不到ownername

CREATE VIEW foo_user WITH (security_barrier = True) AS
    SELECT id, num, word, data FROM foo 
    WHERE foo.ownername = CURRENT_USER;    

現在自動設置所有者名稱:

CREATE OR REPLACE FUNCTION trf_set_owner() RETURNS trigger AS
$$
BEGIN
    IF (TG_OP = 'INSERT') THEN
    NEW.ownername = CURRENT_USER::varchar(64);   
    END IF;
    IF (TG_OP = 'UPDATE') THEN
        NEW.ownername = CURRENT_USER::varchar(64);
    END IF;
    RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

CREATE TRIGGER foo_row_owner
    BEFORE INSERT OR UPDATE ON foo FOR EACH ROW
     EXECUTE PROCEDURE trf_set_owner();

請注意,視圖中不顯示ownername列; 行安全性對用戶不可見。

現在添加一些數據:

INSERT INTO foo_user (num, word, data) VALUES (1, 'asdf', 'cat'), (2, 'qwer', 'dog');


SELECT * FROM foo;
-- normally, this would give an error related to privileges,
-- because we don't allow users to query the underlying table.
-- bypassed here for demo purposes.

 id | num | word | data | ownername
----+-----+------+------+-----------
  1 |   1 | asdf | cat  | admin
  2 |   2 | qwer | dog  | admin
(2 rows)


SELECT * FROM foo_user;

 id | num | word | data
----+-----+------+------
  1 |   1 | asdf | cat
  2 |   2 | qwer | dog
(2 rows)

到現在為止還挺好。

我試過的

如上所述,對於每個用戶, numword必須是唯一的。 不同的所有者具有相同的numword (實際上,我們期望它)沒有問題。

我正在嘗試利用INSERT中的ON CONFLICT子句創建一些后端UPSERT-ish功能。 它正在倒下。

簡單的錯誤示例

首先,一個簡單的失敗插入:

INSERT INTO foo_user (num, word, data) VALUES (2, 'qwer', 'frog');
ERROR:  duplicate key value violates unique constraint "foo_num_word_owner_idx"
DETAIL:  Key (num, word, ownername)=(2, qwer, admin) already exists.

完全期待。 沒有錯。

在沖突中,第一次嘗試

現在我們嘗試讓客戶體驗更順暢:

INSERT INTO foo_user (num, word, data) VALUES (2, 'qwer', 'frog')
    ON CONFLICT DO UPDATE 
    SET data = 'frog'
    WHERE num = 2 AND word = 'qwer';

ERROR:  ON CONFLICT DO UPDATE requires inference specification or constraint name
LINE 2:     ON CONFLICT DO UPDATE
            ^
HINT:  For example, ON CONFLICT (column_name).

是的,就像文檔說的那樣。 它需要知道它破壞了什么規則。 沒問題:

關於沖突,第二次嘗試

INSERT INTO foo_user (num, word, data) VALUES (2, 'qwer', 'frog')
    ON CONFLICT (num, word, ownername) DO UPDATE 
    SET data = 'frog'
    WHERE num = 2 AND word = 'qwer';

ERROR:  column "ownername" does not exist
LINE 2:     ON CONFLICT (num, word, ownername) DO UPDATE

真正。 視圖中不存在所有者名稱。 我們不能降ownername從唯一索引,因為我們完全有理由相信不同的業主有相同的numword值。

在沖突中,第三次嘗試

所以我嘗試將索引轉換為約束,並命名約束:

ALTER TABLE foo 
    ADD CONSTRAINT foo_num_word_owner_crt UNIQUE 
    USING INDEX foo_num_word_owner_idx;

NOTICE:  ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index 
"foo_num_word_owner_idx" to "foo_num_word_owner_crt"

好的,現在來測試:

INSERT INTO foo_user (num, word, data) VALUES (2, 'qwer', 'frog')
    ON CONFLICT ON CONSTRAINT foo_num_word_owner_crt DO UPDATE 
    SET data = 'frog'
    WHERE num = 2 AND word = 'qwer';

ERROR:  constraint "foo_num_word_owner_crt" for table "foo_user" does not exist

嗯,這是有道理的:我們查詢視圖,但指定一個表約束。

結論

現在我沒有想法了。 我們怎么讓ON CONFLICT與這樣的觀點玩得很好? 還是不可能?

我很接近 (舉起拇指和食指)建議我們從視圖切換到具有行級安全性的表,但這相當多的工作(不一定是API破壞者,但仍然)。

任何見解都非常感謝。

您可以通過刪除ON CONFLICT子句並使用手動測試任何索引沖突的INSTEAD OF觸發器來解決問題:

CREATE OR REPLACE FUNCTION trf_set_num_word() RETURNS trigger AS $$
BEGIN
    -- Check if (num, word, ownername) exists by trying an UPDATE
    UPDATE foo SET data = 'frog'
    WHERE num = NEW.num AND word = NEW.word AND ownername = CURRENT_USER::varchar(64);   
    IF FOUND THEN
        RETURN NULL; -- If so, don't INSERT/UPDATE
    END IF;
    RETURN NEW; -- If not, do the INSERT
END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER foo_user_num_word
    INSTEAD OF INSERT OR UPDATE ON foo_user FOR EACH ROW
    EXECUTE PROCEDURE trf_set_num_word();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM