From 4cbac6287126cfb5af46c750cf7828c0d997456c Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 28 Sep 2026 06:55:10 +0900 Subject: [PATCH] Enforce pg_class.relchecks limit (32,767 or INT16_MAX) pg_class.relchecks reports the number of CHECK constraints in a table, and is stored within a smallint. It was possible to define a table that could overflow the counter when specifying more than 32k constraints (new table or adding a constraint). This would cause the table to become undroppable, as this counter should never be negative when dropping these constraints. Author: Matthias van de Meent Reviewed-by: Bertrand Drouvot Reviewed-by: Jian He Reviewed-by: Sivaprasad Discussion: https://postgr.es/m/CAEze2WjSW4qaHYnvkTtbknp85eGEirGJTLMZatotdJJKiA+_hA@mail.gmail.com Backpatch-through: 14 --- doc/src/sgml/limits.sgml | 7 +++++++ src/backend/catalog/heap.c | 29 ++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) 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 + + CHECK constraints per table + + 32,767 + constrained by the size of pg_class.relchecks + + columns in a result set 1,664 diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 346c770e3fc..c735ecaa910 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -56,6 +56,7 @@ #include "catalog/storage.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" +#include "common/int.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" @@ -2258,7 +2259,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, static void StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) { - int numchecks = 0; + int16 numchecks = 0; ListCell *lc; if (cooked_constraints == NIL) @@ -2282,12 +2283,18 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) is_internal, false); break; case CONSTR_CHECK: + if (pg_add_s16_overflow(numchecks, 1, &numchecks)) + ereport(ERROR, + errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many check constraints on relation \"%s\"", + RelationGetRelationName(rel))); + con->conoid = StoreRelCheck(rel, con->name, con->expr, !con->skip_validation, con->is_local, con->inhcount, con->is_no_inherit, is_internal); - numchecks++; + break; default: elog(ERROR, "unrecognized constraint type: %d", @@ -2342,7 +2349,7 @@ AddRelationNewConstraints(Relation rel, int numoldchecks; ParseState *pstate; ParseNamespaceItem *nsitem; - int numchecks; + int16 numchecks; List *checknames; ListCell *cell; Node *expr; @@ -2544,6 +2551,16 @@ AddRelationNewConstraints(Relation rel, checknames = lappend(checknames, ccname); } + /* + * pg_class.relchecks stores this count in an int16, so we should + * avoid overflowing that field + */ + if (pg_add_s16_overflow(numchecks, 1, &numchecks)) + ereport(ERROR, + errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many check constraints on relation \"%s\"", + RelationGetRelationName(rel))); + /* * OK, store it. */ @@ -2551,8 +2568,6 @@ AddRelationNewConstraints(Relation rel, StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local, is_local ? 0 : 1, cdef->is_no_inherit, is_internal); - numchecks++; - cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_CHECK; cooked->conoid = constrOid; @@ -2760,6 +2775,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);