Skip to content

Commit ea91253

Browse files
committed
Avoid trying to restore table ACLs and per-column ACLs in parallel.
Parallel pg_restore has always supposed that ACL items for different objects are independent and can be restored in parallel without conflicts. However, there is one case where this fails: because REVOKE on a table is defined to also revoke the privilege(s) at column level, we can't restore per-column ACLs till after we restore any table-level privileges on their table. Failure to honor this restriction can lead to "tuple concurrently updated" errors during parallel restore, or even to the per-column ACLs silently disappearing because the table-level REVOKE is executed afterwards. To fix, add a dependency from each column-level ACL item to its table's ACL item, if there is one. Note that this doesn't fix the hazard for pre-existing archive files, only for ones made with a corrected pg_dump. Given that the bug's been there quite awhile without field reports, I think this is acceptable. This requires changing the API of pg_dump's dumpACL() function. To keep its argument list from getting even longer, I removed the "CatalogId objCatId" argument, which has been unused for ages. Per report from Justin Pryzby. Back-patch to all supported branches. Discussion: https://postgr.es/m/20200706050129.GW4107@telsasoft.com
1 parent 64fe120 commit ea91253

File tree

3 files changed

+64
-35
lines changed

3 files changed

+64
-35
lines changed

src/bin/pg_dump/common.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
*/
2929
static DumpableObject **dumpIdMap = NULL;
3030
static int allocedDumpIds = 0;
31-
static DumpId lastDumpId = 0;
31+
static DumpId lastDumpId = 0; /* Note: 0 is InvalidDumpId */
3232

3333
/*
3434
* Variables for mapping CatalogId to DumpableObject

src/bin/pg_dump/pg_backup.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,12 @@ typedef struct
233233

234234
typedef int DumpId;
235235

236+
#define InvalidDumpId 0
237+
238+
/*
239+
* Function pointer prototypes for assorted callback methods.
240+
*/
241+
236242
typedef int (*DataDumperPtr) (Archive *AH, void *userArg);
237243

238244
typedef void (*SetupWorkerPtrType) (Archive *AH);

src/bin/pg_dump/pg_dump.c

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,11 @@ static void dumpUserMappings(Archive *fout,
220220
const char *owner, CatalogId catalogId, DumpId dumpId);
221221
static void dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo);
222222

223-
static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
224-
const char *type, const char *name, const char *subname,
225-
const char *nspname, const char *owner,
226-
const char *acls, const char *racls,
227-
const char *initacls, const char *initracls);
223+
static DumpId dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId,
224+
const char *type, const char *name, const char *subname,
225+
const char *nspname, const char *owner,
226+
const char *acls, const char *racls,
227+
const char *initacls, const char *initracls);
228228

229229
static void getDependencies(Archive *fout);
230230
static void BuildArchiveDependencies(Archive *fout);
@@ -2992,7 +2992,7 @@ dumpDatabase(Archive *fout)
29922992
* Dump ACL if any. Note that we do not support initial privileges
29932993
* (pg_init_privs) on databases.
29942994
*/
2995-
dumpACL(fout, dbCatId, dbDumpId, "DATABASE",
2995+
dumpACL(fout, dbDumpId, InvalidDumpId, "DATABASE",
29962996
qdatname, NULL, NULL,
29972997
dba, datacl, rdatacl, "", "");
29982998

@@ -3477,7 +3477,7 @@ dumpBlob(Archive *fout, BlobInfo *binfo)
34773477

34783478
/* Dump ACL if any */
34793479
if (binfo->blobacl && (binfo->dobj.dump & DUMP_COMPONENT_ACL))
3480-
dumpACL(fout, binfo->dobj.catId, binfo->dobj.dumpId, "LARGE OBJECT",
3480+
dumpACL(fout, binfo->dobj.dumpId, InvalidDumpId, "LARGE OBJECT",
34813481
binfo->dobj.name, NULL,
34823482
NULL, binfo->rolname, binfo->blobacl, binfo->rblobacl,
34833483
binfo->initblobacl, binfo->initrblobacl);
@@ -10155,7 +10155,7 @@ dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
1015510155
nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId);
1015610156

1015710157
if (nspinfo->dobj.dump & DUMP_COMPONENT_ACL)
10158-
dumpACL(fout, nspinfo->dobj.catId, nspinfo->dobj.dumpId, "SCHEMA",
10158+
dumpACL(fout, nspinfo->dobj.dumpId, InvalidDumpId, "SCHEMA",
1015910159
qnspname, NULL, NULL,
1016010160
nspinfo->rolname, nspinfo->nspacl, nspinfo->rnspacl,
1016110161
nspinfo->initnspacl, nspinfo->initrnspacl);
@@ -10439,7 +10439,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
1043910439
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1044010440

1044110441
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
10442-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
10442+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1044310443
qtypname, NULL,
1044410444
tyinfo->dobj.namespace->dobj.name,
1044510445
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -10565,7 +10565,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
1056510565
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1056610566

1056710567
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
10568-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
10568+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1056910569
qtypname, NULL,
1057010570
tyinfo->dobj.namespace->dobj.name,
1057110571
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -10637,7 +10637,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
1063710637
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1063810638

1063910639
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
10640-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
10640+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1064110641
qtypname, NULL,
1064210642
tyinfo->dobj.namespace->dobj.name,
1064310643
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -10918,7 +10918,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
1091810918
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1091910919

1092010920
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
10921-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
10921+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1092210922
qtypname, NULL,
1092310923
tyinfo->dobj.namespace->dobj.name,
1092410924
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -11074,7 +11074,7 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
1107411074
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1107511075

1107611076
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
11077-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
11077+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1107811078
qtypname, NULL,
1107911079
tyinfo->dobj.namespace->dobj.name,
1108011080
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -11296,7 +11296,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
1129611296
tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
1129711297

1129811298
if (tyinfo->dobj.dump & DUMP_COMPONENT_ACL)
11299-
dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
11299+
dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE",
1130011300
qtypname, NULL,
1130111301
tyinfo->dobj.namespace->dobj.name,
1130211302
tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl,
@@ -11596,7 +11596,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
1159611596
plang->dobj.catId, 0, plang->dobj.dumpId);
1159711597

1159811598
if (plang->lanpltrusted && plang->dobj.dump & DUMP_COMPONENT_ACL)
11599-
dumpACL(fout, plang->dobj.catId, plang->dobj.dumpId, "LANGUAGE",
11599+
dumpACL(fout, plang->dobj.dumpId, InvalidDumpId, "LANGUAGE",
1160011600
qlanname, NULL, NULL,
1160111601
plang->lanowner, plang->lanacl, plang->rlanacl,
1160211602
plang->initlanacl, plang->initrlanacl);
@@ -12306,7 +12306,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
1230612306
finfo->dobj.catId, 0, finfo->dobj.dumpId);
1230712307

1230812308
if (finfo->dobj.dump & DUMP_COMPONENT_ACL)
12309-
dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, keyword,
12309+
dumpACL(fout, finfo->dobj.dumpId, InvalidDumpId, keyword,
1231012310
funcsig, NULL,
1231112311
finfo->dobj.namespace->dobj.name,
1231212312
finfo->rolname, finfo->proacl, finfo->rproacl,
@@ -14304,7 +14304,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
1430414304
aggsig = format_function_signature(fout, &agginfo->aggfn, true);
1430514305

1430614306
if (agginfo->aggfn.dobj.dump & DUMP_COMPONENT_ACL)
14307-
dumpACL(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId,
14307+
dumpACL(fout, agginfo->aggfn.dobj.dumpId, InvalidDumpId,
1430814308
"FUNCTION", aggsig, NULL,
1430914309
agginfo->aggfn.dobj.namespace->dobj.name,
1431014310
agginfo->aggfn.rolname, agginfo->aggfn.proacl,
@@ -14706,7 +14706,7 @@ dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
1470614706

1470714707
/* Handle the ACL */
1470814708
if (fdwinfo->dobj.dump & DUMP_COMPONENT_ACL)
14709-
dumpACL(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
14709+
dumpACL(fout, fdwinfo->dobj.dumpId, InvalidDumpId,
1471014710
"FOREIGN DATA WRAPPER", qfdwname, NULL,
1471114711
NULL, fdwinfo->rolname,
1471214712
fdwinfo->fdwacl, fdwinfo->rfdwacl,
@@ -14795,7 +14795,7 @@ dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo)
1479514795

1479614796
/* Handle the ACL */
1479714797
if (srvinfo->dobj.dump & DUMP_COMPONENT_ACL)
14798-
dumpACL(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
14798+
dumpACL(fout, srvinfo->dobj.dumpId, InvalidDumpId,
1479914799
"FOREIGN SERVER", qsrvname, NULL,
1480014800
NULL, srvinfo->rolname,
1480114801
srvinfo->srvacl, srvinfo->rsrvacl,
@@ -14988,8 +14988,9 @@ dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo)
1498814988
/*----------
1498914989
* Write out grant/revoke information
1499014990
*
14991-
* 'objCatId' is the catalog ID of the underlying object.
1499214991
* 'objDumpId' is the dump ID of the underlying object.
14992+
* 'altDumpId' can be a second dumpId that the ACL entry must also depend on,
14993+
* or InvalidDumpId if there is no need for a second dependency.
1499314994
* 'type' must be one of
1499414995
* TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
1499514996
* FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT.
@@ -15012,25 +15013,29 @@ dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo)
1501215013
* NB: initacls/initracls are needed because extensions can set privileges on
1501315014
* an object during the extension's script file and we record those into
1501415015
* pg_init_privs as that object's initial privileges.
15016+
*
15017+
* Returns the dump ID assigned to the ACL TocEntry, or InvalidDumpId if
15018+
* no ACL entry was created.
1501515019
*----------
1501615020
*/
15017-
static void
15018-
dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
15021+
static DumpId
15022+
dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId,
1501915023
const char *type, const char *name, const char *subname,
1502015024
const char *nspname, const char *owner,
1502115025
const char *acls, const char *racls,
1502215026
const char *initacls, const char *initracls)
1502315027
{
15028+
DumpId aclDumpId = InvalidDumpId;
1502415029
DumpOptions *dopt = fout->dopt;
1502515030
PQExpBuffer sql;
1502615031

1502715032
/* Do nothing if ACL dump is not enabled */
1502815033
if (dopt->aclsSkip)
15029-
return;
15034+
return InvalidDumpId;
1503015035

1503115036
/* --data-only skips ACLs *except* BLOB ACLs */
1503215037
if (dopt->dataOnly && strcmp(type, "LARGE OBJECT") != 0)
15033-
return;
15038+
return InvalidDumpId;
1503415039

1503515040
sql = createPQExpBuffer();
1503615041

@@ -15062,25 +15067,36 @@ dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
1506215067
if (sql->len > 0)
1506315068
{
1506415069
PQExpBuffer tag = createPQExpBuffer();
15070+
DumpId aclDeps[2];
15071+
int nDeps = 0;
1506515072

1506615073
if (subname)
1506715074
appendPQExpBuffer(tag, "COLUMN %s.%s", name, subname);
1506815075
else
1506915076
appendPQExpBuffer(tag, "%s %s", type, name);
1507015077

15071-
ArchiveEntry(fout, nilCatalogId, createDumpId(),
15078+
aclDeps[nDeps++] = objDumpId;
15079+
if (altDumpId != InvalidDumpId)
15080+
aclDeps[nDeps++] = altDumpId;
15081+
15082+
aclDumpId = createDumpId();
15083+
15084+
ArchiveEntry(fout, nilCatalogId, aclDumpId,
1507215085
ARCHIVE_OPTS(.tag = tag->data,
1507315086
.namespace = nspname,
1507415087
.owner = owner,
1507515088
.description = "ACL",
1507615089
.section = SECTION_NONE,
1507715090
.createStmt = sql->data,
15078-
.deps = &objDumpId,
15079-
.nDeps = 1));
15091+
.deps = aclDeps,
15092+
.nDeps = nDeps));
15093+
1508015094
destroyPQExpBuffer(tag);
1508115095
}
1508215096

1508315097
destroyPQExpBuffer(sql);
15098+
15099+
return aclDumpId;
1508415100
}
1508515101

1508615102
/*
@@ -15406,6 +15422,7 @@ static void
1540615422
dumpTable(Archive *fout, TableInfo *tbinfo)
1540715423
{
1540815424
DumpOptions *dopt = fout->dopt;
15425+
DumpId tableAclDumpId = InvalidDumpId;
1540915426
char *namecopy;
1541015427

1541115428
/*
@@ -15427,11 +15444,12 @@ dumpTable(Archive *fout, TableInfo *tbinfo)
1542715444
const char *objtype =
1542815445
(tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" : "TABLE";
1542915446

15430-
dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
15431-
objtype, namecopy, NULL,
15432-
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
15433-
tbinfo->relacl, tbinfo->rrelacl,
15434-
tbinfo->initrelacl, tbinfo->initrrelacl);
15447+
tableAclDumpId =
15448+
dumpACL(fout, tbinfo->dobj.dumpId, InvalidDumpId,
15449+
objtype, namecopy, NULL,
15450+
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
15451+
tbinfo->relacl, tbinfo->rrelacl,
15452+
tbinfo->initrelacl, tbinfo->initrrelacl);
1543515453
}
1543615454

1543715455
/*
@@ -15515,8 +15533,13 @@ dumpTable(Archive *fout, TableInfo *tbinfo)
1551515533
char *attnamecopy;
1551615534

1551715535
attnamecopy = pg_strdup(fmtId(attname));
15518-
/* Column's GRANT type is always TABLE */
15519-
dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
15536+
15537+
/*
15538+
* Column's GRANT type is always TABLE. Each column ACL depends
15539+
* on the table-level ACL, since we can restore column ACLs in
15540+
* parallel but the table-level ACL has to be done first.
15541+
*/
15542+
dumpACL(fout, tbinfo->dobj.dumpId, tableAclDumpId,
1552015543
"TABLE", namecopy, attnamecopy,
1552115544
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
1552215545
attacl, rattacl, initattacl, initrattacl);

0 commit comments

Comments
 (0)