sqlpostgresqlforeign-keyscheck-constraints

PostgreSQL check constraint for foreign key condition


I have a table of users eg:

create table "user" (
    id serial primary key,
    name text not null,
    superuser boolean not null default false
);

and a table with jobs:

create table job (
    id serial primary key,
    description text
);

the jobs can be assigned to users, but only for superusers. other users cannot have jobs assigned.

So I have a table whereby I see which job was assigned to which user:

create table user_has_job (
    user_id integer references "user"(id),
    job_id integer references job(id),
    constraint user_has_job_pk PRIMARY KEY (user_id, job_id)
);

But I want to create a check constraint that the user_id references a user that has user.superuser = True.

Is that possible? Or is there another solution?


Solution

  • This would work for INSERTS:

    create or replace function is_superuser(int) returns boolean as $$
    select superuser from "user" where id = $1;
    $$ language sql;
    

    And then a check constraint on the user_has_job table:

    create table user_has_job (
        user_id integer references "user"(id),
        job_id integer references job(id),
        constraint user_has_job_pk PRIMARY KEY (user_id, job_id),
        constraint chk_is_superuser check (is_superuser(user_id))
    );
    

    Works for inserts:

    postgres=# insert into "user" (name,superuser) values ('name1',false);
    INSERT 0 1
    postgres=# insert into "user" (name,superuser) values ('name2',true);
    INSERT 0 1
    
    postgres=# insert into job (description) values ('test');
    INSERT 0 1
    postgres=# insert into user_has_job (user_id,job_id) values (1,1);
    ERROR:  new row for relation "user_has_job" violates check constraint "chk_is_superuser"
    DETAIL:  Failing row contains (1, 1).
    postgres=# insert into user_has_job (user_id,job_id) values (2,1);
    INSERT 0 1
    

    However this is possible:

    postgres=# update "user" set superuser=false;
    UPDATE 2
    

    So if you allow updating users you need to create an update trigger on the users table to prevent that if the user has jobs.