PostgreSQL Bugs

Collected from the PG bugs email list.

Bug ID16158
PG Version11.5
OSx86_64 GNU/Linux
Opened2019-12-10 12:27:13+00
Reported byPiotr Jander
StatusNew

Body of first available message related to this bug follows.

The following bug has been logged on the website:

Bug reference:      16158
Logged by:          Piotr Jander
Email address:      (redacted)
PostgreSQL version: 11.5
Operating system:   x86_64 GNU/Linux
Description:        

Summary: I defined a check constraint `project_limits` using
[SQL functions](https://www.postgresql.org/docs/9.1/xfunc-sql.html).
The constraint can be enforced using the commands

```
alter table "projects" add constraint project_limits check(...) not valid;
alter table "projects" validate constraint project_limits;
```

However, it is not enforced on inserts. Consequently, the database can get
into 
an invalid state, as demonstrated below.

The sequence of commands below is complete and self-contained.

Given PostgreSQL 11.5:

```
project_manager=> SELECT version();
                                                 version
---------------------------------------------------------------------------------------------------------
 PostgreSQL 11.5 on x86_64-pc-linux-gnu, compiled by gcc (Debian
6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
(1 row)
```

We create tables `organizations` and `projects`:

```
project_manager=> create table "organizations" ("id" SERIAL NOT NULL PRIMARY
KEY,"limit" INTEGER NOT NULL);
CREATE TABLE
project_manager=> create table "projects" ("id" SERIAL NOT NULL PRIMARY
KEY,"organization_id" INTEGER NOT NULL);
CREATE TABLE
project_manager=> alter table "projects" add constraint "organizations"
foreign key("organization_id") references "organizations"("id");
ALTER TABLE
```

We define SQL functions `org_limit` and `project_count` which will be used
in
the check constraint:

```
project_manager=> create or replace function org_limit(org_id integer)
returns integer as 'select "limit" from "organizations" where "id" = org_id'
language sql;
CREATE FUNCTION
project_manager=> create or replace function project_count(org_id integer)
returns bigint as 'select count(*) from "projects" where "organization_id" =
org_id' language sql;
CREATE FUNCTION
```

The intended constraint is that there should never be more projects in an
organizations that the organization's limit. Before actually adding the
check
constraint, let us arrive at a state which would violate this constraint
(two
projects in an organization which has a limit of 1):

```
project_manager=> insert into "organizations" ("limit") values (1);
INSERT 0 1
project_manager=> select * from "organizations";
 id | limit
----+-------
  1 |     1
(1 row)

project_manager=> insert into "projects" ("organization_id") values (1);
INSERT 0 1
project_manager=> insert into "projects" ("organization_id") values (1);
INSERT 0 1
project_manager=> select * from "projects";
 id | organization_id
----+-----------------
  1 |               1
  2 |               1
(2 rows)
```

Now we finally add the contraint (first as not valid and then we attempt
to
validate it):

```
project_manager=> alter table "projects" add constraint project_limits
check(project_count(organization_id) <= org_limit(organization_id)) not
valid;
ALTER TABLE
project_manager=> alter table "projects" validate constraint
project_limits;
ERROR:  check constraint "project_limits" is violated by some row
```

Indeed, we can manually verify that the constraint is violated:

```
project_manager=> select project_count(1);
 project_count
---------------
             2
(1 row)

project_manager=> select org_limit(1);
 org_limit
-----------
         1
(1 row)
```

After we delete one of the two projects, we can successfully validate the
constraint:

```
project_manager=> delete from "projects" where "id" = 2;
DELETE 1
project_manager=> select * from "projects";
 id | organization_id
----+-----------------
  1 |               1
(1 row)

project_manager=> alter table "projects" validate constraint
project_limits;
ALTER TABLE
```

Before we attempt to violate the constraint again, we confirm that the
constraint is added to the table:

```
project_manager=> \d "projects";
                                 Table "public.projects"
     Column      |  Type   | Collation | Nullable |               Default
-----------------+---------+-----------+----------+--------------------------------------
 id              | integer |           | not null |
nextval('projects_id_seq'::regclass)
 organization_id | integer |           | not null |
Indexes:
    "projects_pkey" PRIMARY KEY, btree (id)
Check constraints:
    "project_limits" CHECK (project_count(organization_id) <=
org_limit(organization_id))
Foreign-key constraints:
    "organizations" FOREIGN KEY (organization_id) REFERENCES
organizations(id)
```

BUG: We can violate the constraint by insert another project.

```
project_manager=> insert into "projects" ("organization_id") values (1);
INSERT 0 1
```

At this point, the `validate constraint` command doesn't catch the
violation
either.

```
project_manager=> alter table "projects" validate constraint
project_limits;
ALTER TABLE
```

Again, we manually verify that the constraint is violated:

```
project_manager=> select project_count(1);
 project_count
---------------
             2
(1 row)

project_manager=> select org_limit(1);
 org_limit
-----------
         1
(1 row)
```

To catch the violation, we need to drop the constraint, add it again, and
validate:

```
project_manager=> alter table "projects" drop constraint project_limits;
ALTER TABLE
project_manager=> alter table "projects" add constraint project_limits
check(project_count(organization_id) <= org_limit(organization_id)) not
valid;
ALTER TABLE
project_manager=> alter table "projects" validate constraint
project_limits;
ERROR:  check constraint "project_limits" is violated by some row
```

My conjecture is that the bug is due to the use of SQL functions in the
check.
However, the
[docs](https://www.postgresql.org/docs/9.4/ddl-constraints.html)
on constraints do not mention any limitations on using such SQL functions
in
checks. If such limitations exist, they should be mentioned in the docs.

Messages

DateAuthorSubject
2019-12-10 12:27:13+00PG Bug reporting formBUG #16158: Check constraints using SQL functions work incorrectly
2019-12-10 15:00:24+00"David G(dot) Johnston"Re: BUG #16158: Check constraints using SQL functions work incorrectly
2019-12-10 15:14:31+00Tom LaneRe: BUG #16158: Check constraints using SQL functions work incorrectly
2019-12-10 15:39:59+00Tom LaneRe: BUG #16158: Check constraints using SQL functions work incorrectly