On Wed, Jun 18, 2025 at 8:13 AM Fujii Masao <masao.fu...@oss.nttdata.com> wrote:
>
> I'm aware of a related open item [1] affecting both v17 and v18,
> but this seems like a separate issue, since it relates to a new v18 feature...
> Or we should treat them the same?
>
I think they are separate issues.

for check constraint that dump willed separately:
check constraint comments will go through dumpConstraint,
dumpTableConstraintComment
for check constraint that dump won't dump separately:
check constraint comments will go through dumpTableSchema,
dumpTableConstraintComment

Similarly we don't need to worry about not-null constraints that are
dumped separately.
dumpConstraint, dumpTableConstraintComment will do the job.

however per comment from collectComments say
"/* We needn't remember comments that don't match an */"

there is no ConstraintInfo for these inlined not-null, that means
``static CommentItem *comments = NULL;``
does not hold any comments for these inlined-null constraints.

so for
CREATE TABLE t (i int);
ALTER TABLE t ADD CONSTRAINT my_not_null NOT NULL i;
COMMENT ON CONSTRAINT my_not_null ON t IS 'my not null';

we can not locate the not-null constraint and use dumpComment to dump
the comments.
dumpComment->findComments will return nothing.
but we need to do the similar thing of dumpCommentExtended
``if (ncomments > 0){}`` code branch.

dumpTableSchema handles dumping of table column definitions, and tells us which
column print_notnull is true.  Since we already know which columns have had
their not-null constraints printed, it makes sense to dump inline not-null
comments here too.

Please check the attached POC patch.
From c586c07a2c2b473f88453c12de0a08190db42a2f Mon Sep 17 00:00:00 2001
From: jian he <jian.universal...@gmail.com>
Date: Wed, 18 Jun 2025 22:35:14 +0800
Subject: [PATCH v1 1/1] fix pg_dump not dump comments on not-null constraints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

dumpComment doesn't handle these inlined NOT NULL constraint comments, because
collectComments doesn't collect them—they aren't associated with a separate
dumpable object.  Rather than modifying collectComments, we manually dump these
inlined not-null constraint comments within dumpTableSchema.

reported by: Fujii Masao <masao(dot)fujii(at)oss(dot)nttdata(dot)com>

discussion: https://postgr.es/m/d50ff977-c728-4e9e-8488-fc2688e08...@oss.nttdata.com
---
 src/bin/pg_dump/pg_dump.c | 111 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 111 insertions(+)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7bc0724cd30..909ce30d553 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -16769,6 +16769,9 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 	char	   *storage;
 	int			j,
 				k;
+	bool	   *notnullprinted;
+	bool		notnull_printed = false;	/* at least one not-null printed */
+	notnullprinted = (bool *) pg_malloc0(tbinfo->numatts * sizeof(bool));
 
 	/* We had better have loaded per-column details about this table */
 	Assert(tbinfo->interesting);
@@ -17016,6 +17019,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 					if (print_notnull)
 					{
+						notnullprinted[j] = true;
 						if (tbinfo->notnull_constrs[j][0] == '\0')
 							appendPQExpBufferStr(q, " NOT NULL");
 						else
@@ -17684,6 +17688,113 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 	if (tbinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
 		dumpTableSecLabel(fout, tbinfo, reltypename);
 
+	/*
+	 * Dump Table not-null constraint comments.
+	 * Invalid not-null constraints are dumped separately.  So we only need to
+	 * handle comments for these "inlined" not-null constraints.
+	 * collectComments function does not collect comments for inlined not-null
+	 * constraints, as they don't have separate dumpable object associated with
+	 * it. Therefore, we need to explicitly dump comments for these "inlined"
+	 * not-null constraints now, since they won't be dumped separately.
+	 */
+	for (j = 0; j < tbinfo->numatts; j++)
+	{
+		if (notnullprinted[j])
+		{
+			notnull_printed = true;
+			break;
+		}
+	}
+
+	if (!fout->dopt->no_comments &&
+		 dopt->dumpSchema &&
+		 fout->remoteVersion >= 180000 &&
+		 notnull_printed)
+	{
+		PGresult   *res;
+		int			i_description;
+		int			i_constrname;
+		bool 		firstitem = true;
+		char	*qtabname;
+		const char 	*descr;
+		const char	*constrname;
+		const char 	*namespace = tbinfo->dobj.namespace->dobj.name;
+		const char 	*owner = tbinfo->rolname;
+		PQExpBuffer query = createPQExpBuffer();
+		PQExpBuffer comments = createPQExpBuffer();
+		PQExpBuffer conprefix = createPQExpBuffer();
+		PQExpBuffer tag = createPQExpBuffer();
+		DumpId	dumpId = tbinfo->dobj.dumpId;
+
+		qtabname = pg_strdup(fmtId(tbinfo->dobj.name));
+		appendPQExpBufferStr(query, "SELECT pt.description, pc.conname\n"
+							"FROM pg_catalog.pg_constraint pc\n"
+							"JOIN pg_catalog.pg_description pt\n"
+							"ON pt.classoid = pc.tableoid AND pc.oid = pt.objoid\n"
+							"WHERE contype = 'n' AND conrelid = ");
+		appendStringLiteralAH(query, fmtQualifiedDumpable(tbinfo), fout);
+
+		appendPQExpBufferStr(query, "::pg_catalog.regclass AND conkey IN (");
+		for (j = 0; j < tbinfo->numatts; j++)
+		{
+			if (notnullprinted[j])
+			{
+				Assert(tbinfo->notnull_constrs[j] != NULL);
+
+				if (firstitem)
+					firstitem = false;
+				else
+					appendPQExpBufferStr(query, ", ");
+
+				appendPQExpBuffer(query, "'{%d}'", j + 1);
+			}
+		}
+		appendPQExpBufferChar(query, ')');
+
+		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+		i_description = PQfnumber(res, "description");
+		i_constrname = PQfnumber(res, "conname");
+
+		for (int i = 0; i < PQntuples(res); i++)
+		{
+			descr = PQgetvalue(res, i, i_description);
+			constrname = PQgetvalue(res, i, i_constrname);
+
+			appendPQExpBuffer(conprefix, "CONSTRAINT %s ON",
+							  fmtId(constrname));
+
+			appendPQExpBuffer(comments, "COMMENT ON %s ", conprefix->data);
+			if (namespace && *namespace)
+				appendPQExpBuffer(comments, "%s.", fmtId(namespace));
+			appendPQExpBuffer(comments, "%s IS ", qtabname);
+			appendStringLiteralAH(comments, descr, fout);
+			appendPQExpBufferStr(comments, ";\n");
+
+			appendPQExpBuffer(tag, "%s %s", conprefix->data, qtabname);
+
+			/* see dumpCommentExtended comments also */
+			ArchiveEntry(fout, nilCatalogId, createDumpId(),
+						ARCHIVE_OPTS(.tag = tag->data,
+									.namespace = namespace,
+									.owner = owner,
+									.description = "COMMENT",
+									.section = SECTION_NONE,
+									.createStmt = comments->data,
+									.deps = &dumpId,
+									.nDeps = 1));
+
+			resetPQExpBuffer(comments);
+			resetPQExpBuffer(tag);
+			resetPQExpBuffer(conprefix);
+		}
+		PQclear(res);
+		destroyPQExpBuffer(query);
+		destroyPQExpBuffer(conprefix);
+		destroyPQExpBuffer(comments);
+		destroyPQExpBuffer(tag);
+		free(qtabname);
+	}
+
 	/* Dump comments on inlined table constraints */
 	for (j = 0; j < tbinfo->ncheck; j++)
 	{
-- 
2.34.1

Reply via email to