Skip to content

Commit 47fb875

Browse files
Álvaro Herrerajianhe-fun
andcommitted
pg_dump: include comments on valid not-null constraints, too
We were missing collecting comments for not-null constraints that are dumped inline with the table definition (i.e., valid ones), because they aren't represented by a separately dumpable object. Fix by creating separate TocEntries for the comments. Co-Authored-By: Jian He <jian.universality@gmail.com> Co-Authored-By: Álvaro Herrera <alvherre@kurilemu.de> Reported-By: Fujii Masao <masao.fujii@oss.nttdata.com> Reviewed-By: Fujii Masao <masao.fujii@oss.nttdata.com> Discussion: https://postgr.es/m/d50ff977-c728-4e9e-8488-fc2688e08754@oss.nttdata.com
1 parent 81ce602 commit 47fb875

File tree

6 files changed

+118
-14
lines changed

6 files changed

+118
-14
lines changed

doc/src/sgml/ref/pg_dump.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,7 @@ PostgreSQL documentation
12811281
materialized views, and foriegn tables.
12821282
Post-data items include definitions of indexes, triggers, rules,
12831283
statistics for indexes, and constraints other than validated check
1284-
constraints.
1284+
and not-null constraints.
12851285
Pre-data items include all other data definition items.
12861286
</para>
12871287
</listitem>

src/bin/pg_dump/pg_dump.c

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ static void buildMatViewRefreshDependencies(Archive *fout);
350350
static void getTableDataFKConstraints(void);
351351
static void determineNotNullFlags(Archive *fout, PGresult *res, int r,
352352
TableInfo *tbinfo, int j,
353-
int i_notnull_name, int i_notnull_invalidoid,
353+
int i_notnull_name,
354+
int i_notnull_comment,
355+
int i_notnull_invalidoid,
354356
int i_notnull_noinherit,
355357
int i_notnull_islocal,
356358
PQExpBuffer *invalidnotnulloids);
@@ -9006,6 +9008,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
90069008
int i_attalign;
90079009
int i_attislocal;
90089010
int i_notnull_name;
9011+
int i_notnull_comment;
90099012
int i_notnull_noinherit;
90109013
int i_notnull_islocal;
90119014
int i_notnull_invalidoid;
@@ -9089,15 +9092,17 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
90899092

90909093
/*
90919094
* Find out any NOT NULL markings for each column. In 18 and up we read
9092-
* pg_constraint to obtain the constraint name. notnull_noinherit is set
9095+
* pg_constraint to obtain the constraint name, and for valid constraints
9096+
* also pg_description to obtain its comment. notnull_noinherit is set
90939097
* according to the NO INHERIT property. For versions prior to 18, we
90949098
* store an empty string as the name when a constraint is marked as
90959099
* attnotnull (this cues dumpTableSchema to print the NOT NULL clause
90969100
* without a name); also, such cases are never NO INHERIT.
90979101
*
90989102
* For invalid constraints, we need to store their OIDs for processing
90999103
* elsewhere, so we bring the pg_constraint.oid value when the constraint
9100-
* is invalid, and NULL otherwise.
9104+
* is invalid, and NULL otherwise. Their comments are handled not here
9105+
* but by collectComments, because they're their own dumpable object.
91019106
*
91029107
* We track in notnull_islocal whether the constraint was defined directly
91039108
* in this table or via an ancestor, for binary upgrade. flagInhAttrs
@@ -9107,13 +9112,16 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
91079112
if (fout->remoteVersion >= 180000)
91089113
appendPQExpBufferStr(q,
91099114
"co.conname AS notnull_name,\n"
9115+
"CASE WHEN co.convalidated THEN pt.description"
9116+
" ELSE NULL END AS notnull_comment,\n"
91109117
"CASE WHEN NOT co.convalidated THEN co.oid "
91119118
"ELSE NULL END AS notnull_invalidoid,\n"
91129119
"co.connoinherit AS notnull_noinherit,\n"
91139120
"co.conislocal AS notnull_islocal,\n");
91149121
else
91159122
appendPQExpBufferStr(q,
91169123
"CASE WHEN a.attnotnull THEN '' ELSE NULL END AS notnull_name,\n"
9124+
"NULL AS notnull_comment,\n"
91179125
"NULL AS notnull_invalidoid,\n"
91189126
"false AS notnull_noinherit,\n"
91199127
"a.attislocal AS notnull_islocal,\n");
@@ -9157,15 +9165,16 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
91579165

91589166
/*
91599167
* In versions 18 and up, we need pg_constraint for explicit NOT NULL
9160-
* entries. Also, we need to know if the NOT NULL for each column is
9161-
* backing a primary key.
9168+
* entries and pg_description to get their comments.
91629169
*/
91639170
if (fout->remoteVersion >= 180000)
91649171
appendPQExpBufferStr(q,
91659172
" LEFT JOIN pg_catalog.pg_constraint co ON "
91669173
"(a.attrelid = co.conrelid\n"
91679174
" AND co.contype = 'n' AND "
9168-
"co.conkey = array[a.attnum])\n");
9175+
"co.conkey = array[a.attnum])\n"
9176+
" LEFT JOIN pg_catalog.pg_description pt ON "
9177+
"(pt.classoid = co.tableoid AND pt.objoid = co.oid)\n");
91699178

91709179
appendPQExpBufferStr(q,
91719180
"WHERE a.attnum > 0::pg_catalog.int2\n"
@@ -9189,6 +9198,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
91899198
i_attalign = PQfnumber(res, "attalign");
91909199
i_attislocal = PQfnumber(res, "attislocal");
91919200
i_notnull_name = PQfnumber(res, "notnull_name");
9201+
i_notnull_comment = PQfnumber(res, "notnull_comment");
91929202
i_notnull_invalidoid = PQfnumber(res, "notnull_invalidoid");
91939203
i_notnull_noinherit = PQfnumber(res, "notnull_noinherit");
91949204
i_notnull_islocal = PQfnumber(res, "notnull_islocal");
@@ -9257,6 +9267,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
92579267
tbinfo->attfdwoptions = (char **) pg_malloc(numatts * sizeof(char *));
92589268
tbinfo->attmissingval = (char **) pg_malloc(numatts * sizeof(char *));
92599269
tbinfo->notnull_constrs = (char **) pg_malloc(numatts * sizeof(char *));
9270+
tbinfo->notnull_comment = (char **) pg_malloc(numatts * sizeof(char *));
92609271
tbinfo->notnull_invalid = (bool *) pg_malloc(numatts * sizeof(bool));
92619272
tbinfo->notnull_noinh = (bool *) pg_malloc(numatts * sizeof(bool));
92629273
tbinfo->notnull_islocal = (bool *) pg_malloc(numatts * sizeof(bool));
@@ -9288,11 +9299,14 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
92889299
determineNotNullFlags(fout, res, r,
92899300
tbinfo, j,
92909301
i_notnull_name,
9302+
i_notnull_comment,
92919303
i_notnull_invalidoid,
92929304
i_notnull_noinherit,
92939305
i_notnull_islocal,
92949306
&invalidnotnulloids);
92959307

9308+
tbinfo->notnull_comment[j] = PQgetisnull(res, r, i_notnull_comment) ?
9309+
NULL : pg_strdup(PQgetvalue(res, r, i_notnull_comment));
92969310
tbinfo->attoptions[j] = pg_strdup(PQgetvalue(res, r, i_attoptions));
92979311
tbinfo->attcollation[j] = atooid(PQgetvalue(res, r, i_attcollation));
92989312
tbinfo->attcompression[j] = *(PQgetvalue(res, r, i_attcompression));
@@ -9704,8 +9718,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
97049718
* 4) The column has a constraint with a known name; in that case
97059719
* notnull_constrs carries that name and dumpTableSchema will print
97069720
* "CONSTRAINT the_name NOT NULL". However, if the name is the default
9707-
* (table_column_not_null), there's no need to print that name in the dump,
9708-
* so notnull_constrs is set to the empty string and it behaves as case 2.
9721+
* (table_column_not_null) and there's no comment on the constraint,
9722+
* there's no need to print that name in the dump, so notnull_constrs
9723+
* is set to the empty string and it behaves as case 2.
97099724
*
97109725
* In a child table that inherits from a parent already containing NOT NULL
97119726
* constraints and the columns in the child don't have their own NOT NULL
@@ -9732,6 +9747,7 @@ static void
97329747
determineNotNullFlags(Archive *fout, PGresult *res, int r,
97339748
TableInfo *tbinfo, int j,
97349749
int i_notnull_name,
9750+
int i_notnull_comment,
97359751
int i_notnull_invalidoid,
97369752
int i_notnull_noinherit,
97379753
int i_notnull_islocal,
@@ -9805,11 +9821,13 @@ determineNotNullFlags(Archive *fout, PGresult *res, int r,
98059821
{
98069822
/*
98079823
* In binary upgrade of inheritance child tables, must have a
9808-
* constraint name that we can UPDATE later.
9824+
* constraint name that we can UPDATE later; same if there's a
9825+
* comment on the constraint.
98099826
*/
9810-
if (dopt->binary_upgrade &&
9811-
!tbinfo->ispartition &&
9812-
!tbinfo->notnull_islocal)
9827+
if ((dopt->binary_upgrade &&
9828+
!tbinfo->ispartition &&
9829+
!tbinfo->notnull_islocal) ||
9830+
!PQgetisnull(res, r, i_notnull_comment))
98139831
{
98149832
tbinfo->notnull_constrs[j] =
98159833
pstrdup(PQgetvalue(res, r, i_notnull_name));
@@ -17686,6 +17704,56 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1768617704
if (tbinfo->dobj.dump & DUMP_COMPONENT_SECLABEL)
1768717705
dumpTableSecLabel(fout, tbinfo, reltypename);
1768817706

17707+
/*
17708+
* Dump comments for not-null constraints that aren't to be dumped
17709+
* separately (those are processed by collectComments/dumpComment).
17710+
*/
17711+
if (!fout->dopt->no_comments && dopt->dumpSchema &&
17712+
fout->remoteVersion >= 180000)
17713+
{
17714+
PQExpBuffer comment = NULL;
17715+
PQExpBuffer tag = NULL;
17716+
17717+
for (j = 0; j < tbinfo->numatts; j++)
17718+
{
17719+
if (tbinfo->notnull_constrs[j] != NULL &&
17720+
tbinfo->notnull_comment[j] != NULL)
17721+
{
17722+
if (comment == NULL)
17723+
{
17724+
comment = createPQExpBuffer();
17725+
tag = createPQExpBuffer();
17726+
}
17727+
else
17728+
{
17729+
resetPQExpBuffer(comment);
17730+
resetPQExpBuffer(tag);
17731+
}
17732+
17733+
appendPQExpBuffer(comment, "COMMENT ON CONSTRAINT %s ON %s IS ",
17734+
fmtId(tbinfo->notnull_constrs[j]), qualrelname);
17735+
appendStringLiteralAH(comment, tbinfo->notnull_comment[j], fout);
17736+
appendPQExpBufferStr(comment, ";\n");
17737+
17738+
appendPQExpBuffer(tag, "CONSTRAINT %s ON %s",
17739+
fmtId(tbinfo->notnull_constrs[j]), qrelname);
17740+
17741+
ArchiveEntry(fout, nilCatalogId, createDumpId(),
17742+
ARCHIVE_OPTS(.tag = tag->data,
17743+
.namespace = tbinfo->dobj.namespace->dobj.name,
17744+
.owner = tbinfo->rolname,
17745+
.description = "COMMENT",
17746+
.section = SECTION_NONE,
17747+
.createStmt = comment->data,
17748+
.deps = &(tbinfo->dobj.dumpId),
17749+
.nDeps = 1));
17750+
}
17751+
}
17752+
17753+
destroyPQExpBuffer(comment);
17754+
destroyPQExpBuffer(tag);
17755+
}
17756+
1768917757
/* Dump comments on inlined table constraints */
1769017758
for (j = 0; j < tbinfo->ncheck; j++)
1769117759
{

src/bin/pg_dump/pg_dump.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ typedef struct _tableInfo
365365
* there isn't one on this column. If
366366
* empty string, unnamed constraint
367367
* (pre-v17) */
368+
char **notnull_comment; /* comment thereof */
368369
bool *notnull_invalid; /* true for NOT NULL NOT VALID */
369370
bool *notnull_noinh; /* NOT NULL is NO INHERIT */
370371
bool *notnull_islocal; /* true if NOT NULL has local definition */

src/bin/pg_dump/t/002_pg_dump.pl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1191,7 +1191,9 @@
11911191
) INHERITS (dump_test.test_table_nn, dump_test.test_table_nn_2);
11921192
ALTER TABLE dump_test.test_table_nn ADD CONSTRAINT nn NOT NULL col1 NOT VALID;
11931193
ALTER TABLE dump_test.test_table_nn_chld1 VALIDATE CONSTRAINT nn;
1194-
ALTER TABLE dump_test.test_table_nn_chld2 VALIDATE CONSTRAINT nn;',
1194+
ALTER TABLE dump_test.test_table_nn_chld2 VALIDATE CONSTRAINT nn;
1195+
COMMENT ON CONSTRAINT nn ON dump_test.test_table_nn IS \'nn comment is valid\';
1196+
COMMENT ON CONSTRAINT nn ON dump_test.test_table_nn_chld2 IS \'nn_chld2 comment is valid\';',
11951197
regexp => qr/^
11961198
\QALTER TABLE dump_test.test_table_nn\E \n^\s+
11971199
\QADD CONSTRAINT nn NOT NULL col1 NOT VALID;\E
@@ -1205,6 +1207,34 @@
12051207
},
12061208
},
12071209

1210+
# This constraint is invalid therefore it goes in SECTION_POST_DATA
1211+
'COMMENT ON CONSTRAINT ON test_table_nn' => {
1212+
regexp => qr/^
1213+
\QCOMMENT ON CONSTRAINT nn ON dump_test.test_table_nn IS\E
1214+
/xm,
1215+
like => {
1216+
%full_runs, %dump_test_schema_runs, section_post_data => 1,
1217+
},
1218+
unlike => {
1219+
exclude_dump_test_schema => 1,
1220+
only_dump_measurement => 1,
1221+
},
1222+
},
1223+
1224+
# This constraint is valid therefore it goes in SECTION_PRE_DATA
1225+
'COMMENT ON CONSTRAINT ON test_table_chld2' => {
1226+
regexp => qr/^
1227+
\QCOMMENT ON CONSTRAINT nn ON dump_test.test_table_nn_chld2 IS\E
1228+
/xm,
1229+
like => {
1230+
%full_runs, %dump_test_schema_runs, section_pre_data => 1,
1231+
},
1232+
unlike => {
1233+
exclude_dump_test_schema => 1,
1234+
only_dump_measurement => 1,
1235+
},
1236+
},
1237+
12081238
'CONSTRAINT NOT NULL / NOT VALID (child1)' => {
12091239
regexp => qr/^
12101240
\QCREATE TABLE dump_test.test_table_nn_chld1 (\E\n

src/test/regress/expected/constraints.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,8 @@ EXECUTE get_nnconstraint_info('{constr_parent3, constr_child3}');
16591659
constr_parent3 | constr_parent3_a_not_null | t | t | 0
16601660
(2 rows)
16611661

1662+
COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_parent2 IS 'this constraint is invalid';
1663+
COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_child2 IS 'this constraint is valid';
16621664
DEALLOCATE get_nnconstraint_info;
16631665
-- end NOT NULL NOT VALID
16641666
-- Comments

src/test/regress/sql/constraints.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,9 @@ create table constr_parent3 (a int not null);
997997
create table constr_child3 () inherits (constr_parent2, constr_parent3);
998998
EXECUTE get_nnconstraint_info('{constr_parent3, constr_child3}');
999999

1000+
COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_parent2 IS 'this constraint is invalid';
1001+
COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_child2 IS 'this constraint is valid';
1002+
10001003
DEALLOCATE get_nnconstraint_info;
10011004

10021005
-- end NOT NULL NOT VALID

0 commit comments

Comments
 (0)