Thread
Commits
-
Doc: back-patch documentation about limitations of CHECK constraints.
- af4006f0097a 10.12 landed
- 7ef59c9b1839 9.6.17 landed
- 6bf23e8c6e06 9.5.21 landed
- 49b83f647434 11.7 landed
- 3ddd8ee7167f 9.4.26 landed
-
BUG #16158: Check constraints using SQL functions work incorrectly
The Post Office <noreply@postgresql.org> — 2019-12-10T12:27:13Z
The following bug has been logged on the website: Bug reference: 16158 Logged by: Piotr Jander Email address: pjander2@gmail.com 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. -
Re: BUG #16158: Check constraints using SQL functions work incorrectly
David G. Johnston <david.g.johnston@gmail.com> — 2019-12-10T15:00:24Z
On Tue, Dec 10, 2019 at 5:28 AM PG Bug reporting form < noreply@postgresql.org> wrote: > The following bug has been logged on the website: > > Bug reference: 16158 > Logged by: Piotr Jander > Email address: pjander2@gmail.com > PostgreSQL version: 11.5 > Operating system: x86_64 GNU/Linux > Description: > > [...] > > 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. > You should read the most current version of the documentation for a feature before reporting a bug. In this case the warnings you desired have been added to more recently versions of the documentation. https://www.postgresql.org/docs/12/ddl-constraints.html Short answer, check constraints are designed to work only with data present on the table upon which they are defined. Use triggers to work with other tables. David J.
-
Re: BUG #16158: Check constraints using SQL functions work incorrectly
Tom Lane <tgl@sss.pgh.pa.us> — 2019-12-10T15:14:31Z
PG Bug reporting form <noreply@postgresql.org> writes: > Summary: I defined a check constraint `project_limits` using > [SQL functions](https://www.postgresql.org/docs/9.1/xfunc-sql.html). This is not a bug; you've ignored the restriction that check constraints must be immutable. See the "Notes" at the bottom of this section: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS In the particular case at hand, I think the behavior you're complaining about stems from the fact that the CHECK condition is evaluated before the actual row insertion has happened. regards, tom lane
-
Re: BUG #16158: Check constraints using SQL functions work incorrectly
Tom Lane <tgl@sss.pgh.pa.us> — 2019-12-10T15:39:59Z
"David G. Johnston" <david.g.johnston@gmail.com> writes: > You should read the most current version of the documentation for a feature > before reporting a bug. In this case the warnings you desired have been > added to more recently versions of the documentation. Hm, I was thinking that that text had been there for awhile --- certainly it's not a new restriction. I wonder why we didn't back-patch it? regards, tom lane