From 5e59290402518db5b2876ecd627c7d79f73eaa44 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 28 Sep 2026 06:55:02 +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 | 28 +++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/limits.sgml b/doc/src/sgml/limits.sgml index 7f7eb51985e..44a14947bf2 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 1c188b7a0ff..f0a159c1b77 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2331,7 +2331,7 @@ StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum, static void StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) { - int numchecks = 0; + int16 numchecks = 0; ListCell *lc; if (cooked_constraints == NIL) @@ -2355,12 +2355,18 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) is_internal); 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->is_enforced, !con->skip_validation, con->is_local, con->inhcount, con->is_no_inherit, is_internal); - numchecks++; + break; default: @@ -2418,7 +2424,7 @@ AddRelationNewConstraints(Relation rel, int numoldchecks; ParseState *pstate; ParseNamespaceItem *nsitem; - int numchecks; + int16 numchecks; List *checknames; List *nnnames; Node *expr; @@ -2619,6 +2625,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. */ @@ -2628,8 +2644,6 @@ AddRelationNewConstraints(Relation rel, is_local ? 0 : 1, cdef->is_no_inherit, is_internal); - numchecks++; - cooked = palloc_object(CookedConstraint); cooked->contype = CONSTR_CHECK; cooked->conoid = constrOid; @@ -3199,6 +3213,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);