Hi, sending v2 to the list as well. This version changes the limit check from `>=` to `>`, allowing the documented maximum of 32,767 CHECK constraints.
On Thu, Sep 24, 2026 at 10:34 PM Matthias van de Meent <[email protected]> wrote: > > Hi, > > There's probably no user that finds good use for this many check > constraints, but users *can* create > 2^15 CHECK constraints on a > table, and doing so will overflow the int16 (sqltype int2) column in > which the relation's count of those is stored. > > This causes various issues, such as possible WARNING spam to users > that need to load that relation into relcache, and an inability to > drop the relation because dropping the table requires the constraints > to be dropped first, and dropping a constraint decrements the counter > that has a check is in place to avoid the counter ever dropping below > zero (with an error if you try to decrement non-positive counter > values). > > I think we should forbid creating such large amounts of CHECK > constraints (as attached, backpatch-safe), or drop the "relchecks" > field wholesale/replace it with a 'haschecks' field. > > Also attached is an SQL script that shows the issue, and which doesn't > fail in the unsafe manner once PG is patched with the attached patch. > > Note: the attached patch does not solve any issues in existing > databases with overflowed relchecks fields. > > > Kind regards, > > Matthias van de Meent > Databricks (https://www.databricks.com)
From 8b003be09c64bdc565e2478a2d2c3897efd7cbdc Mon Sep 17 00:00:00 2001 From: Matthias van de Meent <[email protected]> Date: Thu, 24 Sep 2026 17:02:25 +0200 Subject: [PATCH v1] Enforce the pg_class.relchecks limit Previously, the count could overflow if sufficiently many CHECK constraints were added to the table, which (if new CHECKs kept getting added) could cause the table to become undroppable due to enforcement of non-negative counts when dropping these constraints. --- doc/src/sgml/limits.sgml | 7 +++++++ src/backend/catalog/heap.c | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/doc/src/sgml/limits.sgml b/doc/src/sgml/limits.sgml index f26f4466719..0e9a0b4e94e 100644 --- a/doc/src/sgml/limits.sgml +++ b/doc/src/sgml/limits.sgml @@ -62,6 +62,13 @@ below</entry> </row> + <row> + <entry><literal>CHECK</literal> constraints per table</entry> + <!-- 2^15 - 1 --> + <entry>32,767</entry> + <entry>constrained by the size of pg_class.relchecks</entry> + </row> + <row> <entry>columns in a result set</entry> <entry>1,664</entry> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index a107193d7ab..7bbf6de4f2d 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2360,6 +2360,12 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) con->is_local, con->inhcount, con->is_no_inherit, is_internal); numchecks++; + + if (numchecks > PG_INT16_MAX) + ereport(ERROR, + errmsg("too many check constraints on relation \"%s\"", + RelationGetQualifiedRelationName(rel))); + break; default: @@ -2629,6 +2635,11 @@ AddRelationNewConstraints(Relation rel, numchecks++; + if (numchecks > PG_INT16_MAX) + ereport(ERROR, + errmsg("too many check constraints on relation \"%s\"", + RelationGetQualifiedRelationName(rel))); + cooked = palloc_object(CookedConstraint); cooked->contype = CONSTR_CHECK; cooked->conoid = constrOid; @@ -3198,6 +3209,10 @@ SetRelationNumChecks(Relation rel, int numchecks) if (relStruct->relchecks != numchecks) { + if (numchecks > INT16_MAX || numchecks < 0) + elog(ERROR, "invalid new relchecks %d for relation %u", + numchecks, RelationGetRelid(rel)); + relStruct->relchecks = numchecks; CatalogTupleUpdate(relrel, &reltup->t_self, reltup); -- 2.54.0 (Apple Git-157)
