加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

PostgreSQL – ON CONFLICT UPDATE with view with columns子集

发布时间:2020-12-13 15:51:54 所属栏目:百科 来源:网络整理
导读:目前正在运行9.5.3版.当然是更新计划. 我有一个PostgreSQL数据库,其架构早于表行级安全性(即CREATE POLICY …).使用视图实现行级安全性.通过仅选择具有与CURRENT_USER匹配的所有者名称的行,在视图中完成安全性. 我正在尝试使用这样的视图构建一个upsert查询.
目前正在运行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.请注意,用户看不到所有者名称.

CREATE VIEW foo_user WITH (security_barrier = True) AS
    SELECT id,num,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,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,data) VALUES (2,'frog');
ERROR:  duplicate key value violates unique constraint "foo_num_word_owner_idx"
DETAIL:  Key (num,ownername)=(2,qwer,admin) already exists.

完全期待.没有错.

在冲突中,第一次尝试

现在我们尝试让客户体验更顺畅:

INSERT INTO foo_user (num,'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,'frog')
    ON CONFLICT (num,ownername) DO UPDATE 
    SET data = 'frog'
    WHERE num = 2 AND word = 'qwer';

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

真正.视图中不存在所有者名称.我们不能从唯一索引中删除所有者名称,因为我们完全希望不同的所有者具有相同的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,'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,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();

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读