[英]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));
對於每個用戶, word
和num
的組合必須是唯一的。
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)
到現在為止還挺好。
如上所述,對於每個用戶, num
和word
必須是唯一的。 不同的所有者具有相同的num
和word
(實際上,我們期望它)沒有問題。
我正在嘗試利用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
從唯一索引,因為我們完全有理由相信不同的業主有相同的num
和word
值。
所以我嘗試將索引轉換為約束,並命名約束:
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.