Re: FK Constraint with ON DELETE SET DEFAULT cascading as table owner

From: rob stone <floriparob(at)gmail(dot)com>
To: Brad Leupen <qcompson1(at)gmail(dot)com>, "pgsql-general(at)postgresql(dot)org" <pgsql-general(at)postgresql(dot)org>
Subject: Re: FK Constraint with ON DELETE SET DEFAULT cascading as table owner
Date: 2019-02-02 04:17:23
Message-ID: 83fcd2bb48fe0bdc523c95dc7eddeeece34fe421.camel@gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

Hello,

On Fri, 2019-02-01 at 14:01 +0000, Brad Leupen wrote:
> Hello,
>
> We are using RLS on Postgres 11 to implement multi tenancy in our
> application. We have a tenant table whose id matches the tenant’s
> user role. Each table has a tenant_id FKA that defaults to
> “current_user”. All of our foreign key constraints are multipart
> (tenant_id + row_id). So far this works great except when we try to
> clean up FKA references on deletion. Here’s a script that
> demonstrates the issue in an empty database:
>
>
> CREATE ROLE tenant1;
>
> ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON
> TABLES TO tenant1;
> ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON
> SEQUENCES TO tenant1;
>
> CREATE TABLE tenant (
> id TEXT NOT NULL DEFAULT user PRIMARY KEY
> );
>
> CREATE TABLE foo (
> tenant TEXT REFERENCES tenant NOT NULL DEFAULT user,
> id SERIAL NOT NULL,
> default_bar INT,
> PRIMARY KEY (tenant, id)
> );
>
> CREATE TABLE bar (
> tenant TEXT REFERENCES tenant NOT NULL DEFAULT user,
> id SERIAL NOT NULL,
> foo_id INT,
> PRIMARY KEY (tenant, id),
> constraint foo FOREIGN KEY (tenant, foo_id) REFERENCES foo
> (tenant, id) ON DELETE CASCADE
> );
>
> ALTER TABLE foo ADD CONSTRAINT default_bar FOREIGN KEY (tenant,
> default_bar) REFERENCES bar (tenant, id) ON DELETE SET DEFAULT;
>
> ALTER TABLE foo ENABLE ROW LEVEL SECURITY;
> ALTER TABLE bar ENABLE ROW LEVEL SECURITY;
>
> CREATE POLICY tenant_tenant on tenant USING (id = current_user) WITH
> CHECK (id = current_user);
> CREATE POLICY foo_tenant on foo USING (tenant = current_user) WITH
> CHECK (tenant = current_user);
> CREATE POLICY bar_tenant on bar USING (tenant = current_user) WITH
> CHECK (tenant = current_user);
>
> SET ROLE tenant1;
>
> INSERT INTO tenant DEFAULT VALUES;
> INSERT INTO foo DEFAULT VALUES;
> INSERT INTO bar ( foo_id ) (SELECT id FROM foo );
> UPDATE foo SET default_bar = ( SELECT id FROM bar );
> DELETE FROM bar;
>
> This script winds up failing because the “user” default value on
> foo.tenant evaluates to the table owner, not the active user role of
> “tenant1”. Is this the expected behavior? The desired outcome, after
> deleting from bar, would be for foo’s tenant to remain “tenant1” and
> its default_bar value be set to null. Is there another, cleaner way
> to achieve this?
>
> Thank you!
> Brad

I ran your script. Output:-

postgres 11.1 => select * from tenant;
id
---------
tenant1
(1 row)

postgres 11.1 => select * from foo;
tenant | id | default_bar
---------+----+-------------
tenant1 | 1 | 1
(1 row)

postgres 11.1 => select * from bar;
tenant | id | foo_id
---------+----+--------
tenant1 | 1 | 1
(1 row)

postgres 11.1 => delete from bar;
ERROR: insert or update on table "foo" violates foreign key constraint
"foo_tenant_fkey"
DETAIL: Key is not present in table "tenant".
postgres 11.1 =>

Your foreign key constraint is defined as:-

default_bar ==> FOREIGN KEY (tenant, default_bar) REFERENCES
bar(tenant, id) ON DELETE SET DEFAULT

If you don't specify a "default" it uses NULL.
There is no tenant.id that is NULL.
So, the foreign key validation fails.

AFAICT, it is working as intended.

Cheers,
Robert

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message David G. Johnston 2019-02-02 04:59:01 FK Constraint with ON DELETE SET DEFAULT cascading as table owner
Previous Message ceuro 2019-02-01 14:34:56 Re: pgexpress 4.60 vita voom