From 021c19c4adc95044743b9fbb26d4bac9b7488019 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Thu, 24 Sep 2026 17:02:25 +0200
Subject: [PATCH v3] 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 | 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</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 1c188b7a0ff..a3919985503 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);
-- 
2.54.0 (Apple Git-157)

