Skip to content

Commit b3f0e05

Browse files
committed
Reduce number of commands dumpTableSchema emits for binary upgrade.
Avoid issuing a separate SQL UPDATE command for each column when directly manipulating pg_attribute contents in binary upgrade mode. With the separate updates, we triggered a relcache invalidation with each update. For a table with N columns, that causes O(N^2) relcache bloat in txn_size mode because the table's newly-created relcache entry can't be flushed till end of transaction. Reducing the number of commands should make it marginally faster as well as avoiding that problem. While at it, likewise avoid issuing a separate UPDATE on pg_constraint for each inherited constraint. This is less exciting, first because inherited (non-partitioned) constraints are relatively rare, and second because the backend has a good deal of trouble anyway with restoring tables containing many such constraints, due to MergeConstraintsIntoExisting being horribly inefficient. But it seems more consistent to do it this way here too, and it surely can't hurt. In passing, fix one place in dumpTableSchema that failed to use ONLY in ALTER TABLE. That's not a live bug, but it's inconsistent. Also avoid silently casting away const from string literals. Per report from Justin Pryzby. Back-patch to v17 where txn_size mode was introduced. Discussion: https://postgr.es/m/ZqEND4ZcTDBmcv31@pryzbyj2023
1 parent 0393f54 commit b3f0e05

File tree

2 files changed

+99
-37
lines changed

2 files changed

+99
-37
lines changed

src/bin/pg_dump/pg_dump.c

Lines changed: 98 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15670,6 +15670,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1567015670
DumpOptions *dopt = fout->dopt;
1567115671
PQExpBuffer q = createPQExpBuffer();
1567215672
PQExpBuffer delq = createPQExpBuffer();
15673+
PQExpBuffer extra = createPQExpBuffer();
1567315674
char *qrelname;
1567415675
char *qualrelname;
1567515676
int numParents;
@@ -15736,7 +15737,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1573615737
char *partkeydef = NULL;
1573715738
char *ftoptions = NULL;
1573815739
char *srvname = NULL;
15739-
char *foreign = "";
15740+
const char *foreign = "";
1574015741

1574115742
/*
1574215743
* Set reltypename, and collect any relkind-specific data that we
@@ -16094,70 +16095,130 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1609416095
tbinfo->relkind == RELKIND_FOREIGN_TABLE ||
1609516096
tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
1609616097
{
16098+
bool firstitem;
16099+
16100+
/*
16101+
* Drop any dropped columns. Merge the pg_attribute manipulations
16102+
* into a single SQL command, so that we don't cause repeated
16103+
* relcache flushes on the target table. Otherwise we risk O(N^2)
16104+
* relcache bloat while dropping N columns.
16105+
*/
16106+
resetPQExpBuffer(extra);
16107+
firstitem = true;
1609716108
for (j = 0; j < tbinfo->numatts; j++)
1609816109
{
1609916110
if (tbinfo->attisdropped[j])
1610016111
{
16101-
appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate dropped column.\n");
16102-
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_attribute\n"
16103-
"SET attlen = %d, "
16104-
"attalign = '%c', attbyval = false\n"
16105-
"WHERE attname = ",
16112+
if (firstitem)
16113+
{
16114+
appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate dropped columns.\n"
16115+
"UPDATE pg_catalog.pg_attribute\n"
16116+
"SET attlen = v.dlen, "
16117+
"attalign = v.dalign, "
16118+
"attbyval = false\n"
16119+
"FROM (VALUES ");
16120+
firstitem = false;
16121+
}
16122+
else
16123+
appendPQExpBufferStr(q, ",\n ");
16124+
appendPQExpBufferChar(q, '(');
16125+
appendStringLiteralAH(q, tbinfo->attnames[j], fout);
16126+
appendPQExpBuffer(q, ", %d, '%c')",
1610616127
tbinfo->attlen[j],
1610716128
tbinfo->attalign[j]);
16108-
appendStringLiteralAH(q, tbinfo->attnames[j], fout);
16109-
appendPQExpBufferStr(q, "\n AND attrelid = ");
16110-
appendStringLiteralAH(q, qualrelname, fout);
16111-
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
16112-
16113-
if (tbinfo->relkind == RELKIND_RELATION ||
16114-
tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
16115-
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
16116-
qualrelname);
16117-
else
16118-
appendPQExpBuffer(q, "ALTER FOREIGN TABLE ONLY %s ",
16119-
qualrelname);
16120-
appendPQExpBuffer(q, "DROP COLUMN %s;\n",
16129+
/* The ALTER ... DROP COLUMN commands must come after */
16130+
appendPQExpBuffer(extra, "ALTER %sTABLE ONLY %s ",
16131+
foreign, qualrelname);
16132+
appendPQExpBuffer(extra, "DROP COLUMN %s;\n",
1612116133
fmtId(tbinfo->attnames[j]));
1612216134
}
16123-
else if (!tbinfo->attislocal[j])
16135+
}
16136+
if (!firstitem)
16137+
{
16138+
appendPQExpBufferStr(q, ") v(dname, dlen, dalign)\n"
16139+
"WHERE attrelid = ");
16140+
appendStringLiteralAH(q, qualrelname, fout);
16141+
appendPQExpBufferStr(q, "::pg_catalog.regclass\n"
16142+
" AND attname = v.dname;\n");
16143+
/* Now we can issue the actual DROP COLUMN commands */
16144+
appendBinaryPQExpBuffer(q, extra->data, extra->len);
16145+
}
16146+
16147+
/*
16148+
* Fix up inherited columns. As above, do the pg_attribute
16149+
* manipulations in a single SQL command.
16150+
*/
16151+
firstitem = true;
16152+
for (j = 0; j < tbinfo->numatts; j++)
16153+
{
16154+
if (!tbinfo->attisdropped[j] &&
16155+
!tbinfo->attislocal[j])
1612416156
{
16125-
appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate inherited column.\n");
16126-
appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_attribute\n"
16127-
"SET attislocal = false\n"
16128-
"WHERE attname = ");
16157+
if (firstitem)
16158+
{
16159+
appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate inherited columns.\n");
16160+
appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_attribute\n"
16161+
"SET attislocal = false\n"
16162+
"WHERE attrelid = ");
16163+
appendStringLiteralAH(q, qualrelname, fout);
16164+
appendPQExpBufferStr(q, "::pg_catalog.regclass\n"
16165+
" AND attname IN (");
16166+
firstitem = false;
16167+
}
16168+
else
16169+
appendPQExpBufferStr(q, ", ");
1612916170
appendStringLiteralAH(q, tbinfo->attnames[j], fout);
16130-
appendPQExpBufferStr(q, "\n AND attrelid = ");
16131-
appendStringLiteralAH(q, qualrelname, fout);
16132-
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
1613316171
}
1613416172
}
16173+
if (!firstitem)
16174+
appendPQExpBufferStr(q, ");\n");
1613516175

1613616176
/*
1613716177
* Add inherited CHECK constraints, if any.
1613816178
*
1613916179
* For partitions, they were already dumped, and conislocal
1614016180
* doesn't need fixing.
16181+
*
16182+
* As above, issue only one direct manipulation of pg_constraint.
16183+
* Although it is tempting to merge the ALTER ADD CONSTRAINT
16184+
* commands into one as well, refrain for now due to concern about
16185+
* possible backend memory bloat if there are many such
16186+
* constraints.
1614116187
*/
16188+
resetPQExpBuffer(extra);
16189+
firstitem = true;
1614216190
for (k = 0; k < tbinfo->ncheck; k++)
1614316191
{
1614416192
ConstraintInfo *constr = &(tbinfo->checkexprs[k]);
1614516193

1614616194
if (constr->separate || constr->conislocal || tbinfo->ispartition)
1614716195
continue;
1614816196

16149-
appendPQExpBufferStr(q, "\n-- For binary upgrade, set up inherited constraint.\n");
16197+
if (firstitem)
16198+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set up inherited constraints.\n");
1615016199
appendPQExpBuffer(q, "ALTER %sTABLE ONLY %s ADD CONSTRAINT %s %s;\n",
1615116200
foreign, qualrelname,
1615216201
fmtId(constr->dobj.name),
1615316202
constr->condef);
16154-
appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_constraint\n"
16155-
"SET conislocal = false\n"
16156-
"WHERE contype = 'c' AND conname = ");
16157-
appendStringLiteralAH(q, constr->dobj.name, fout);
16158-
appendPQExpBufferStr(q, "\n AND conrelid = ");
16159-
appendStringLiteralAH(q, qualrelname, fout);
16160-
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
16203+
/* Update pg_constraint after all the ALTER TABLEs */
16204+
if (firstitem)
16205+
{
16206+
appendPQExpBufferStr(extra, "UPDATE pg_catalog.pg_constraint\n"
16207+
"SET conislocal = false\n"
16208+
"WHERE contype = 'c' AND conrelid = ");
16209+
appendStringLiteralAH(extra, qualrelname, fout);
16210+
appendPQExpBufferStr(extra, "::pg_catalog.regclass\n");
16211+
appendPQExpBufferStr(extra, " AND conname IN (");
16212+
firstitem = false;
16213+
}
16214+
else
16215+
appendPQExpBufferStr(extra, ", ");
16216+
appendStringLiteralAH(extra, constr->dobj.name, fout);
16217+
}
16218+
if (!firstitem)
16219+
{
16220+
appendPQExpBufferStr(extra, ");\n");
16221+
appendBinaryPQExpBuffer(q, extra->data, extra->len);
1616116222
}
1616216223

1616316224
if (numParents > 0 && !tbinfo->ispartition)
@@ -16344,7 +16405,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1634416405
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE &&
1634516406
tbinfo->attfdwoptions[j][0] != '\0')
1634616407
appendPQExpBuffer(q,
16347-
"ALTER FOREIGN TABLE %s ALTER COLUMN %s OPTIONS (\n"
16408+
"ALTER FOREIGN TABLE ONLY %s ALTER COLUMN %s OPTIONS (\n"
1634816409
" %s\n"
1634916410
");\n",
1635016411
qualrelname,
@@ -16445,6 +16506,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1644516506

1644616507
destroyPQExpBuffer(q);
1644716508
destroyPQExpBuffer(delq);
16509+
destroyPQExpBuffer(extra);
1644816510
free(qrelname);
1644916511
free(qualrelname);
1645016512
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@
11541154

11551155
'ALTER FOREIGN TABLE foreign_table ALTER COLUMN c1 OPTIONS' => {
11561156
regexp => qr/^
1157-
\QALTER FOREIGN TABLE dump_test.foreign_table ALTER COLUMN c1 OPTIONS (\E\n
1157+
\QALTER FOREIGN TABLE ONLY dump_test.foreign_table ALTER COLUMN c1 OPTIONS (\E\n
11581158
\s+\Qcolumn_name 'col1'\E\n
11591159
\Q);\E\n
11601160
/xm,

0 commit comments

Comments
 (0)