Skip to content

Commit d8c05af

Browse files
committed
Fix pg_dump's handling of circular dependencies in views.
pg_dump's traditional solution for breaking a circular dependency involving a view was to create the view with CREATE TABLE and then later issue CREATE RULE "_RETURN" ... to convert the table to a view, relying on the backend's very very ancient code that supports making views that way. We've wanted to get rid of that kluge for a long time, but the thing that finally motivates doing something about it is the recognition that this method fails with the --clean option, because it leads to issuing DROP RULE "_RETURN" followed by DROP TABLE --- and the backend won't let you drop a view's _RETURN rule. Instead, let's break circular dependencies by initially creating the view using CREATE VIEW AS SELECT NULL::columntype AS columnname, ... (so that it has the right column names and types to support external references, but no dependencies beyond the column data types), and then later dumping the ON SELECT rule using the spelling CREATE OR REPLACE VIEW. This method wasn't available when this code was originally written, but it's been possible since PG 7.3, so it seems fine to start relying on it now. To solve the --clean problem, make the dropStmt for an ON SELECT rule be CREATE OR REPLACE VIEW with the same dummy target list as above. In this way, during the DROP phase, we first reduce the view to have no extra dependencies, and then we can drop it entirely when we've gotten rid of whatever had a circular dependency on it. (Note: this should work adequately well with the --if-exists option, since the CREATE OR REPLACE VIEW will go through whether the view exists or not. It could fail if the view exists with a conflicting column set, but we don't really support --clean against a non-matching database anyway.) This allows cleaning up some other kluges inside pg_dump, notably that we don't need a notion of reloptions attached to a rule anymore. Although this is a bug fix, commit to HEAD only for now. The problem's existed for a long time and we've had relatively few complaints, so it doesn't really seem worth taking risks to fix it in the back branches. We might revisit that choice if no problems emerge. Discussion: <19092.1479325184@sss.pgh.pa.us>
1 parent ac88898 commit d8c05af

File tree

3 files changed

+140
-59
lines changed

3 files changed

+140
-59
lines changed

src/bin/pg_dump/pg_dump.c

Lines changed: 136 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5486,7 +5486,7 @@ getTables(Archive *fout, int *numTables)
54865486
tblinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
54875487

54885488
tblinfo[i].interesting = tblinfo[i].dobj.dump ? true : false;
5489-
5489+
tblinfo[i].dummy_view = false; /* might get set during sort */
54905490
tblinfo[i].postponed_def = false; /* might get set during sort */
54915491

54925492
/*
@@ -6207,16 +6207,6 @@ getRules(Archive *fout, int *numRules)
62076207
}
62086208
else
62096209
ruleinfo[i].separate = true;
6210-
6211-
/*
6212-
* If we're forced to break a dependency loop by dumping a view as a
6213-
* table and separate _RETURN rule, we'll move the view's reloptions
6214-
* to the rule. (This is necessary because tables and views have
6215-
* different valid reloptions, so we can't apply the options until the
6216-
* backend knows it's a view.) Otherwise the rule's reloptions stay
6217-
* NULL.
6218-
*/
6219-
ruleinfo[i].reloptions = NULL;
62206210
}
62216211

62226212
PQclear(res);
@@ -13977,6 +13967,54 @@ createViewAsClause(Archive *fout, TableInfo *tbinfo)
1397713967
return result;
1397813968
}
1397913969

13970+
/*
13971+
* Create a dummy AS clause for a view. This is used when the real view
13972+
* definition has to be postponed because of circular dependencies.
13973+
* We must duplicate the view's external properties -- column names and types
13974+
* (including collation) -- so that it works for subsequent references.
13975+
*
13976+
* This returns a new buffer which must be freed by the caller.
13977+
*/
13978+
static PQExpBuffer
13979+
createDummyViewAsClause(Archive *fout, TableInfo *tbinfo)
13980+
{
13981+
PQExpBuffer result = createPQExpBuffer();
13982+
int j;
13983+
13984+
appendPQExpBufferStr(result, "SELECT");
13985+
13986+
for (j = 0; j < tbinfo->numatts; j++)
13987+
{
13988+
if (j > 0)
13989+
appendPQExpBufferChar(result, ',');
13990+
appendPQExpBufferStr(result, "\n ");
13991+
13992+
appendPQExpBuffer(result, "NULL::%s", tbinfo->atttypnames[j]);
13993+
13994+
/*
13995+
* Must add collation if not default for the type, because CREATE OR
13996+
* REPLACE VIEW won't change it
13997+
*/
13998+
if (OidIsValid(tbinfo->attcollation[j]))
13999+
{
14000+
CollInfo *coll;
14001+
14002+
coll = findCollationByOid(tbinfo->attcollation[j]);
14003+
if (coll)
14004+
{
14005+
/* always schema-qualify, don't try to be smart */
14006+
appendPQExpBuffer(result, " COLLATE %s.",
14007+
fmtId(coll->dobj.namespace->dobj.name));
14008+
appendPQExpBufferStr(result, fmtId(coll->dobj.name));
14009+
}
14010+
}
14011+
14012+
appendPQExpBuffer(result, " AS %s", fmtId(tbinfo->attnames[j]));
14013+
}
14014+
14015+
return result;
14016+
}
14017+
1398014018
/*
1398114019
* dumpTableSchema
1398214020
* write the declaration (not data) of one user-defined table or view
@@ -14010,6 +14048,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
1401014048
{
1401114049
PQExpBuffer result;
1401214050

14051+
/*
14052+
* Note: keep this code in sync with the is_view case in dumpRule()
14053+
*/
14054+
1401314055
reltypename = "VIEW";
1401414056

1401514057
/*
@@ -14026,17 +14068,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
1402614068
tbinfo->dobj.catId.oid, false);
1402714069

1402814070
appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
14029-
if (nonemptyReloptions(tbinfo->reloptions))
14071+
if (tbinfo->dummy_view)
14072+
result = createDummyViewAsClause(fout, tbinfo);
14073+
else
1403014074
{
14031-
appendPQExpBufferStr(q, " WITH (");
14032-
appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
14033-
appendPQExpBufferChar(q, ')');
14075+
if (nonemptyReloptions(tbinfo->reloptions))
14076+
{
14077+
appendPQExpBufferStr(q, " WITH (");
14078+
appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
14079+
appendPQExpBufferChar(q, ')');
14080+
}
14081+
result = createViewAsClause(fout, tbinfo);
1403414082
}
14035-
result = createViewAsClause(fout, tbinfo);
1403614083
appendPQExpBuffer(q, " AS\n%s", result->data);
1403714084
destroyPQExpBuffer(result);
1403814085

14039-
if (tbinfo->checkoption != NULL)
14086+
if (tbinfo->checkoption != NULL && !tbinfo->dummy_view)
1404014087
appendPQExpBuffer(q, "\n WITH %s CHECK OPTION", tbinfo->checkoption);
1404114088
appendPQExpBufferStr(q, ";\n");
1404214089

@@ -15648,6 +15695,7 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
1564815695
{
1564915696
DumpOptions *dopt = fout->dopt;
1565015697
TableInfo *tbinfo = rinfo->ruletable;
15698+
bool is_view;
1565115699
PQExpBuffer query;
1565215700
PQExpBuffer cmd;
1565315701
PQExpBuffer delcmd;
@@ -15666,6 +15714,12 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
1566615714
if (!rinfo->separate)
1566715715
return;
1566815716

15717+
/*
15718+
* If it's an ON SELECT rule, we want to print it as a view definition,
15719+
* instead of a rule.
15720+
*/
15721+
is_view = (rinfo->ev_type == '1' && rinfo->is_instead);
15722+
1566915723
/*
1567015724
* Make sure we are in proper schema.
1567115725
*/
@@ -15676,20 +15730,50 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
1567615730
delcmd = createPQExpBuffer();
1567715731
labelq = createPQExpBuffer();
1567815732

15679-
appendPQExpBuffer(query,
15680-
"SELECT pg_catalog.pg_get_ruledef('%u'::pg_catalog.oid) AS definition",
15681-
rinfo->dobj.catId.oid);
15682-
15683-
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
15684-
15685-
if (PQntuples(res) != 1)
15733+
if (is_view)
1568615734
{
15687-
write_msg(NULL, "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned\n",
15688-
rinfo->dobj.name, tbinfo->dobj.name);
15689-
exit_nicely(1);
15735+
PQExpBuffer result;
15736+
15737+
/*
15738+
* We need OR REPLACE here because we'll be replacing a dummy view.
15739+
* Otherwise this should look largely like the regular view dump code.
15740+
*/
15741+
appendPQExpBuffer(cmd, "CREATE OR REPLACE VIEW %s",
15742+
fmtId(tbinfo->dobj.name));
15743+
if (nonemptyReloptions(tbinfo->reloptions))
15744+
{
15745+
appendPQExpBufferStr(cmd, " WITH (");
15746+
appendReloptionsArrayAH(cmd, tbinfo->reloptions, "", fout);
15747+
appendPQExpBufferChar(cmd, ')');
15748+
}
15749+
result = createViewAsClause(fout, tbinfo);
15750+
appendPQExpBuffer(cmd, " AS\n%s", result->data);
15751+
destroyPQExpBuffer(result);
15752+
if (tbinfo->checkoption != NULL)
15753+
appendPQExpBuffer(cmd, "\n WITH %s CHECK OPTION",
15754+
tbinfo->checkoption);
15755+
appendPQExpBufferStr(cmd, ";\n");
1569015756
}
15757+
else
15758+
{
15759+
/* In the rule case, just print pg_get_ruledef's result verbatim */
15760+
appendPQExpBuffer(query,
15761+
"SELECT pg_catalog.pg_get_ruledef('%u'::pg_catalog.oid)",
15762+
rinfo->dobj.catId.oid);
15763+
15764+
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
15765+
15766+
if (PQntuples(res) != 1)
15767+
{
15768+
write_msg(NULL, "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned\n",
15769+
rinfo->dobj.name, tbinfo->dobj.name);
15770+
exit_nicely(1);
15771+
}
1569115772

15692-
printfPQExpBuffer(cmd, "%s\n", PQgetvalue(res, 0, 0));
15773+
printfPQExpBuffer(cmd, "%s\n", PQgetvalue(res, 0, 0));
15774+
15775+
PQclear(res);
15776+
}
1569315777

1569415778
/*
1569515779
* Add the command to alter the rules replication firing semantics if it
@@ -15716,25 +15800,34 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
1571615800
}
1571715801

1571815802
/*
15719-
* Apply view's reloptions when its ON SELECT rule is separate.
15803+
* DROP must be fully qualified in case same name appears in pg_catalog
1572015804
*/
15721-
if (nonemptyReloptions(rinfo->reloptions))
15805+
if (is_view)
1572215806
{
15723-
appendPQExpBuffer(cmd, "ALTER VIEW %s SET (",
15807+
/*
15808+
* We can't DROP a view's ON SELECT rule. Instead, use CREATE OR
15809+
* REPLACE VIEW to replace the rule with something with minimal
15810+
* dependencies.
15811+
*/
15812+
PQExpBuffer result;
15813+
15814+
appendPQExpBuffer(delcmd, "CREATE OR REPLACE VIEW %s.",
15815+
fmtId(tbinfo->dobj.namespace->dobj.name));
15816+
appendPQExpBuffer(delcmd, "%s",
15817+
fmtId(tbinfo->dobj.name));
15818+
result = createDummyViewAsClause(fout, tbinfo);
15819+
appendPQExpBuffer(delcmd, " AS\n%s;\n", result->data);
15820+
destroyPQExpBuffer(result);
15821+
}
15822+
else
15823+
{
15824+
appendPQExpBuffer(delcmd, "DROP RULE %s ",
15825+
fmtId(rinfo->dobj.name));
15826+
appendPQExpBuffer(delcmd, "ON %s.",
15827+
fmtId(tbinfo->dobj.namespace->dobj.name));
15828+
appendPQExpBuffer(delcmd, "%s;\n",
1572415829
fmtId(tbinfo->dobj.name));
15725-
appendReloptionsArrayAH(cmd, rinfo->reloptions, "", fout);
15726-
appendPQExpBufferStr(cmd, ");\n");
1572715830
}
15728-
15729-
/*
15730-
* DROP must be fully qualified in case same name appears in pg_catalog
15731-
*/
15732-
appendPQExpBuffer(delcmd, "DROP RULE %s ",
15733-
fmtId(rinfo->dobj.name));
15734-
appendPQExpBuffer(delcmd, "ON %s.",
15735-
fmtId(tbinfo->dobj.namespace->dobj.name));
15736-
appendPQExpBuffer(delcmd, "%s;\n",
15737-
fmtId(tbinfo->dobj.name));
1573815831

1573915832
appendPQExpBuffer(labelq, "RULE %s",
1574015833
fmtId(rinfo->dobj.name));
@@ -15761,8 +15854,6 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
1576115854
tbinfo->rolname,
1576215855
rinfo->dobj.catId, 0, rinfo->dobj.dumpId);
1576315856

15764-
PQclear(res);
15765-
1576615857
free(tag);
1576715858
destroyPQExpBuffer(query);
1576815859
destroyPQExpBuffer(cmd);

src/bin/pg_dump/pg_dump.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ typedef struct _tableInfo
287287
int relpages; /* table's size in pages (from pg_class) */
288288

289289
bool interesting; /* true if need to collect more data */
290+
bool dummy_view; /* view's real definition must be postponed */
290291
bool postponed_def; /* matview must be postponed into post-data */
291292

292293
/*
@@ -364,8 +365,6 @@ typedef struct _ruleInfo
364365
char ev_enabled;
365366
bool separate; /* TRUE if must dump as separate item */
366367
/* separate is always true for non-ON SELECT rules */
367-
char *reloptions; /* options specified by WITH (...) */
368-
/* reloptions is only set if we need to dump the options with the rule */
369368
} RuleInfo;
370369

371370
typedef struct _triggerInfo

src/bin/pg_dump/pg_dump_sort.c

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,7 @@ repairViewRuleLoop(DumpableObject *viewobj,
786786
{
787787
/* remove rule's dependency on view */
788788
removeObjectDependency(ruleobj, viewobj->dumpId);
789+
/* flags on the two objects are already set correctly for this case */
789790
}
790791

791792
/*
@@ -805,27 +806,17 @@ repairViewRuleMultiLoop(DumpableObject *viewobj,
805806
{
806807
TableInfo *viewinfo = (TableInfo *) viewobj;
807808
RuleInfo *ruleinfo = (RuleInfo *) ruleobj;
808-
int i;
809809

810810
/* remove view's dependency on rule */
811811
removeObjectDependency(viewobj, ruleobj->dumpId);
812-
/* pretend view is a plain table and dump it that way */
813-
viewinfo->relkind = 'r'; /* RELKIND_RELATION */
812+
/* mark view to be printed with a dummy definition */
813+
viewinfo->dummy_view = true;
814814
/* mark rule as needing its own dump */
815815
ruleinfo->separate = true;
816-
/* move any reloptions from view to rule */
817-
if (viewinfo->reloptions)
818-
{
819-
ruleinfo->reloptions = viewinfo->reloptions;
820-
viewinfo->reloptions = NULL;
821-
}
822816
/* put back rule's dependency on view */
823817
addObjectDependency(ruleobj, viewobj->dumpId);
824818
/* now that rule is separate, it must be post-data */
825819
addObjectDependency(ruleobj, postDataBoundId);
826-
/* also, any triggers on the view must be dumped after the rule */
827-
for (i = 0; i < viewinfo->numTriggers; i++)
828-
addObjectDependency(&(viewinfo->triggers[i].dobj), ruleobj->dumpId);
829820
}
830821

831822
/*

0 commit comments

Comments
 (0)