1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-30 21:42:05 +03:00

Fix INSERT ON CONFLICT UPDATE through a view that isn't just SELECT *.

When expanding an updatable view that is an INSERT's target, the rewriter
failed to rewrite Vars in the ON CONFLICT UPDATE clause.  This accidentally
worked if the view was just "SELECT * FROM ...", as the transformation
would be a no-op in that case.  With more complicated view targetlists,
this omission would often lead to "attribute ... has the wrong type" errors
or even crashes, as reported by Mario De Frutos Dieguez.

Fix by adding code to rewriteTargetView to fix up the data structure
correctly.  The easiest way to update the exclRelTlist list is to rebuild
it from scratch looking at the new target relation, so factor the code
for that out of transformOnConflictClause to make it sharable.

In passing, avoid duplicate permissions checks against the EXCLUDED
pseudo-relation, and prevent useless view expansion of that relation's
dummy RTE.  The latter is only known to happen (after this patch) in cases
where the query would fail later due to not having any INSTEAD OF triggers
for the view.  But by exactly that token, it would create an unintended
and very poorly tested state of the query data structure, so it seems like
a good idea to prevent it from happening at all.

This has been broken since ON CONFLICT was introduced, so back-patch
to 9.5.

Dean Rasheed, based on an earlier patch by Amit Langote;
comment-kibitzing and back-patching by me

Discussion: https://postgr.es/m/CAFYwGJ0xfzy8jaK80hVN2eUWr6huce0RU8AgU04MGD00igqkTg@mail.gmail.com
This commit is contained in:
Tom Lane
2018-08-04 19:38:58 -04:00
parent f5b4bb8812
commit b484bffe7d
5 changed files with 514 additions and 57 deletions

View File

@ -963,9 +963,6 @@ transformOnConflictClause(ParseState *pstate,
if (onConflictClause->action == ONCONFLICT_UPDATE)
{
Relation targetrel = pstate->p_target_relation;
Var *var;
TargetEntry *te;
int attno;
/*
* All INSERT expressions have been parsed, get ready for potentially
@ -974,75 +971,36 @@ transformOnConflictClause(ParseState *pstate,
pstate->p_is_insert = false;
/*
* Add range table entry for the EXCLUDED pseudo relation; relkind is
* Add range table entry for the EXCLUDED pseudo relation. relkind is
* set to composite to signal that we're not dealing with an actual
* relation.
* relation, and no permission checks are required on it. (We'll
* check the actual target relation, instead.)
*/
exclRte = addRangeTableEntryForRelation(pstate,
targetrel,
makeAlias("excluded", NIL),
false, false);
exclRte->relkind = RELKIND_COMPOSITE_TYPE;
exclRte->requiredPerms = 0;
/* other permissions fields in exclRte are already empty */
exclRelIndex = list_length(pstate->p_rtable);
/*
* Build a targetlist representing the columns of the EXCLUDED pseudo
* relation. Have to be careful to use resnos that correspond to
* attnos of the underlying relation.
*/
for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++)
{
Form_pg_attribute attr = targetrel->rd_att->attrs[attno];
char *name;
if (attr->attisdropped)
{
/*
* can't use atttypid here, but it doesn't really matter what
* type the Const claims to be.
*/
var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
name = "";
}
else
{
var = makeVar(exclRelIndex, attno + 1,
attr->atttypid, attr->atttypmod,
attr->attcollation,
0);
name = pstrdup(NameStr(attr->attname));
}
te = makeTargetEntry((Expr *) var,
attno + 1,
name,
false);
/* don't require select access yet */
exclRelTlist = lappend(exclRelTlist, te);
}
/*
* Add a whole-row-Var entry to support references to "EXCLUDED.*".
* Like the other entries in exclRelTlist, its resno must match the
* Var's varattno, else the wrong things happen while resolving
* references in setrefs.c. This is against normal conventions for
* targetlists, but it's okay since we don't use this as a real tlist.
*/
var = makeVar(exclRelIndex, InvalidAttrNumber,
targetrel->rd_rel->reltype,
-1, InvalidOid, 0);
te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true);
exclRelTlist = lappend(exclRelTlist, te);
/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
exclRelIndex);
/*
* Add EXCLUDED and the target RTE to the namespace, so that they can
* be used in the UPDATE statement.
* be used in the UPDATE subexpressions.
*/
addRTEtoQuery(pstate, exclRte, false, true, true);
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
false, true, true);
/*
* Now transform the UPDATE subexpressions.
*/
onConflictSet =
transformUpdateTargetList(pstate, onConflictClause->targetList);
@ -1067,6 +1025,74 @@ transformOnConflictClause(ParseState *pstate,
}
/*
* BuildOnConflictExcludedTargetlist
* Create target list for the EXCLUDED pseudo-relation of ON CONFLICT,
* representing the columns of targetrel with varno exclRelIndex.
*
* Note: Exported for use in the rewriter.
*/
List *
BuildOnConflictExcludedTargetlist(Relation targetrel,
Index exclRelIndex)
{
List *result = NIL;
int attno;
Var *var;
TargetEntry *te;
/*
* Note that resnos of the tlist must correspond to attnos of the
* underlying relation, hence we need entries for dropped columns too.
*/
for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
{
Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
char *name;
if (attr->attisdropped)
{
/*
* can't use atttypid here, but it doesn't really matter what type
* the Const claims to be.
*/
var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
name = "";
}
else
{
var = makeVar(exclRelIndex, attno + 1,
attr->atttypid, attr->atttypmod,
attr->attcollation,
0);
name = pstrdup(NameStr(attr->attname));
}
te = makeTargetEntry((Expr *) var,
attno + 1,
name,
false);
result = lappend(result, te);
}
/*
* Add a whole-row-Var entry to support references to "EXCLUDED.*". Like
* the other entries in the EXCLUDED tlist, its resno must match the Var's
* varattno, else the wrong things happen while resolving references in
* setrefs.c. This is against normal conventions for targetlists, but
* it's okay since we don't use this as a real tlist.
*/
var = makeVar(exclRelIndex, InvalidAttrNumber,
targetrel->rd_rel->reltype,
-1, InvalidOid, 0);
te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true);
result = lappend(result, te);
return result;
}
/*
* count_rowexpr_columns -
* get number of columns contained in a ROW() expression;