diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml
index e3ba73a9a8..d53cbf810f 100644
--- a/doc/src/sgml/bki.sgml
+++ b/doc/src/sgml/bki.sgml
@@ -122,10 +122,7 @@
if they are fixed-width and are not preceded by any nullable column.
Where this rule is inadequate, you can force correct marking by using
BKI_FORCE_NOT_NULL
- and BKI_FORCE_NULL annotations as needed. But note
- that NOT NULL constraints are only enforced in the
- executor, not against tuples that are generated by random C code,
- so care is still needed when manually creating or updating catalog rows.
+ and BKI_FORCE_NULL annotations as needed.
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index a5f5bc46a9..674e6a8321 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -18,6 +18,8 @@
#include "access/htup_details.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
+#include "catalog/pg_subscription.h"
+#include "catalog/pg_subscription_rel.h"
#include "executor/executor.h"
#include "utils/rel.h"
@@ -164,6 +166,53 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
ExecDropSingleTupleTableSlot(slot);
}
+/*
+ * Subroutine to verify that catalog constraints are honored.
+ *
+ * Tuples inserted via CatalogTupleInsert/CatalogTupleUpdate are generally
+ * "hand made", so that it's possible that they fail to satisfy constraints
+ * that would be checked if they were being inserted by the executor. That's
+ * a coding error, so we only bother to check for it in assert-enabled builds.
+ */
+#ifdef USE_ASSERT_CHECKING
+
+static void
+CatalogTupleCheckConstraints(Relation heapRel, HeapTuple tup)
+{
+ /*
+ * Currently, the only constraints implemented for system catalogs are
+ * attnotnull constraints.
+ */
+ if (HeapTupleHasNulls(tup))
+ {
+ TupleDesc tupdesc = RelationGetDescr(heapRel);
+ bits8 *bp = tup->t_data->t_bits;
+
+ for (int attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute thisatt = TupleDescAttr(tupdesc, attnum);
+
+ /*
+ * Through an embarrassing oversight, pre-v13 installations have
+ * pg_subscription.subslotname and pg_subscription_rel.srsublsn
+ * marked as attnotnull, which they should not be. Ignore those
+ * flags.
+ */
+ Assert(!(thisatt->attnotnull && att_isnull(attnum, bp) &&
+ !((thisatt->attrelid == SubscriptionRelationId &&
+ thisatt->attnum == Anum_pg_subscription_subslotname) ||
+ (thisatt->attrelid == SubscriptionRelRelationId &&
+ thisatt->attnum == Anum_pg_subscription_rel_srsublsn))));
+ }
+ }
+}
+
+#else /* !USE_ASSERT_CHECKING */
+
+#define CatalogTupleCheckConstraints(heapRel, tup) ((void) 0)
+
+#endif /* USE_ASSERT_CHECKING */
+
/*
* CatalogTupleInsert - do heap and indexing work for a new catalog tuple
*
@@ -182,6 +231,8 @@ CatalogTupleInsert(Relation heapRel, HeapTuple tup)
CatalogIndexState indstate;
Oid oid;
+ CatalogTupleCheckConstraints(heapRel, tup);
+
indstate = CatalogOpenIndexes(heapRel);
oid = simple_heap_insert(heapRel, tup);
@@ -206,6 +257,8 @@ CatalogTupleInsertWithInfo(Relation heapRel, HeapTuple tup,
{
Oid oid;
+ CatalogTupleCheckConstraints(heapRel, tup);
+
oid = simple_heap_insert(heapRel, tup);
CatalogIndexInsert(indstate, tup);
@@ -229,6 +282,8 @@ CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
{
CatalogIndexState indstate;
+ CatalogTupleCheckConstraints(heapRel, tup);
+
indstate = CatalogOpenIndexes(heapRel);
simple_heap_update(heapRel, otid, tup);
@@ -249,6 +304,8 @@ void
CatalogTupleUpdateWithInfo(Relation heapRel, ItemPointer otid, HeapTuple tup,
CatalogIndexState indstate)
{
+ CatalogTupleCheckConstraints(heapRel, tup);
+
simple_heap_update(heapRel, otid, tup);
CatalogIndexInsert(indstate, tup);