是否需要在引用之前提交插入

Does an insert need to be committed prior to referencing it

场景

我有两个 table,foobarbar 引用了 foo 中的字段(外键关系)。

快速预览“在我所在的地方遇见我”:我终于开始意识到“引用”实际上可能意味着“指向”。换句话说,它不仅仅是“我保证有一个匹配值”,而是 bar 承载了一个指向 foo.

中记录的指针

正在“同时”将记录插入 foobar

用户将通过单个函数调用“同时”插入这些内容,从而实现所有这些操作。在函数体中,有一个必须遵循的严格顺序。插入的顺序由“bar references foo”这一事实决定;所以foo首先需要“到位”。

但是,在 bar 中插入匹配值,例如 insert into bar (column_1) values (same_value_used_for_foo) 不起作用,尽管我使用了用于创建 foo 记录的相同值(字面意思是相同的参数值我用来完成这种插入组合的功能)。鉴于 bar references foo.

这肯定会失败,这对大多数人来说可能是显而易见的

尽管如此,这种方法的好处是我认为我可以使用匹配值来避免担心 foo 记录准备好被“引用”时的细节”。这就是它开始陷入困境的地方: references 实际上意味着......好吧,“引用”更多的是本着“指向”(如果不是精确地“指向”)而不是“匹配”(其中副本的价值可能很有效)。

下一个天真的迭代,将 select 新插入的值 from foobar:

insert into bar (x) values( (select foo.x from foo where ...whatever it takes) )

这会起作用,因为 postgres 将创建一个引用,而不是 select 语句中返回值的副本。

最“惯用”、健壮和快速的可能是我在 insert into foo.

之后引用的 postgres returns

问题

这是我开始问问题的地方,

...我什么时候可以引用 foo 记录以便将记录插入 bar 而不会“背叛”外键约束?

更具体地说,**插入 foo 的语句是否需要“提交”;在我引用它之前? **

我本可以迭代代码来找到解决方案,但本着“一劳永逸”的精神,我更好地理解了真正引用列值的含义在另一个 table 中,我想这个问题的答案会有帮助。

最后,为了理解 postgres 安全和权限的上下文,鉴于 references xselect (x) 的不同权限,将其建模为 [=106 是否正确=]gres 在一个人只有 references x 权限时防止“取消引用 x”?

附录 - 问题的迭代

从两个 Adrian Klaver and Daniel Verite 收到的输入,我怀疑答案将在于我的 table + view/insert/update/delete 授予的权限或我的行级策略。

我收到一个“外键违规错误”,该错误依赖于 bazzz 中是否存在记录;第三个 table.

违规错误

ERROR:  insert or update on table "bar" violates foreign key constraint "fk_bar_link_bazzz"
DETAIL:  Key is not present in table "bazzz".
CONTEXT:  SQL statement "insert into core.bar(
            owner_id,
            provider,
            project_id
            ) values (
            _current_user,
            input.provider,
            input.project_id
        ) on conflict do nothing"

被违反的约束:

constraint fk_bar_link_bazzz
    foreign key (owner_id, project_id) references bazzz(user_id, project_id)

-- where as an aside, 
-- the bazz.user_id and bazz.project_id reference the users and
-- projects table id fields respectively

但是,在起草此 post 之前,我可以通过“关注”project_id 值来确认 bazzz 记录确实存在postgres 将“丢失”(通过 view 确认):

-- pgTap test that passes
-- role webuser

    return next is(
        (permission::text, project_id),
        ('owner'::text, test_project_id),
        '✅ with the expected project_id and owner permission')
        from api.bazzz
        where bazzz.project_id = test_project_id;

-- role api, 
-- I can confirm the presence of the expected owner_id and project_id
-- values.

我还确保 运行 使用 security definer 的函数,此外,断言 current_user = api.

   ASSERT current_user::text = 'api',
          'Unexpected current_user: %', current_user;

没有太多理由,我的下一步是审查行级政策;否则什么可能会阻止此交易向前推进?

最终分辨率

当更新插入被触发时的 RLS 策略以及未完全按预期进行的测试最终解决了问题。但是,我只能通过此处的输入来解决问题。答案归功于给出的两个同样有用的输入中的第一个。

注意政策:

-- update
create policy "Api can update when user has access to the project"
    on core.tokens
    for update
    to api
    -- what records the system provides access prior to update
    using (...)
    -- what the values are allowed to be
    with check (...);

... at what point can I reference the foo record in order to insert a record into bar without "betraying" the foreign key constraint?

More concretely, ** does the insert statement into foo need to be "commit;" prior to my referencing it?

从同一个会话(即通过同一个连接到数据库),可以立即引用。

在另一个会话中,在插入会话提交之前无法看到它。

However, inserting a matching value in bar e.g., insert into bar (column_1) values (same_value_used_for_foo) does not work

确实有效。

举个例子,假设您有这些表

create table foo (id serial primary key, something text);

create table bar(some_text text, foo_id int references foo(id));

典型的 two-table 插入序列如下所示。

begin;

insert into foo(something) values('abcd') returning id;
 id 
----
  1

insert into bar values('ghij', 1);

commit;

如果您尝试在引用端插入一个 non-existing 值,那么它会失败:

insert into bar values('ghij', 2);
ERROR:  insert or update on table "bar" violates foreign key constraint "bar_foo_id_fkey"
DETAIL:  Key (foo_id)=(2) is not present in table "foo".

单笔交易示例:

create table parent (id integer, fld_1 varchar unique);
create table child (id integer primary key, parent_fk varchar references parent(fld_1), child_fld varchar);
BEGIN;
insert into parent values (1, 'dog');
insert into child values(1, 'dog', 'ranger');
COMMIT;
select * from parent;
 id | fld_1 
----+-------
  1 | dog
select * from child;
 id | parent_fk | child_fld 
----+-----------+-----------
  1 | dog       | ranger

不同的并发会话(事务):

--Session one
BEGIN;
insert into parent values (2, 'cat');
INSERT 0 1

--Session two
BEGIN;
insert into child values (2, 'cat', 'mayhem');
ERROR:  insert or update on table "child" violates foreign key constraint "child_parent_fk_fkey"
DETAIL:  Key (parent_fk)=(cat) is not present in table "parent".

--Session one
COMMIT;

--Session two
ROLLBACK;
BEGIN;
insert into child values (2, 'cat', 'mayhem');
INSERT 0 1
COMMIT;

因此您可以在一个会话中的单个事务中执行多个 inserts/updates。如果您希望其他 session/transactions 中的子 table(s) 看到父 table 中的引用值,则需要提交对父 table 的插入或更新.

你对外键的理解有点偏差。 Postgres 中的 FK 是使用检查匹配的 FK 上的系统约束触发器来处理的。这可以在 ALTER TABLE where you can disable the triggers if needed, though not advised. You can also change the behavior of the FK trigger by deferring it's application per DEFERRABLE here CREATE TABLE.

中看到