mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Fix several bugs related to ON CONFLICT's EXCLUDED pseudo relation.
Four related issues: 1) attnos/varnos/resnos for EXCLUDED were out of sync when a column after one dropped in the underlying relation was referenced. 2) References to whole-row variables (i.e. EXCLUDED.*) lead to errors. 3) It was possible to reference system columns in the EXCLUDED pseudo relations, even though they would not have valid contents. 4) References to EXCLUDED were rewritten by the RLS machinery, as EXCLUDED was treated as if it were the underlying relation. To fix the first two issues, generate the excluded targetlist with dropped columns in mind and add an entry for whole row variables. Instead of unconditionally adding a wholerow entry we could pull up the expression if needed, but doing it unconditionally seems simpler. The wholerow entry is only really needed for ruleutils/EXPLAIN support anyway. The remaining two issues are addressed by changing the EXCLUDED RTE to have relkind = composite. That fits with EXCLUDED not actually being a real relation, and allows to treat it differently in the relevant places. scanRTEForColumn now skips looking up system columns when the RTE has a composite relkind; fireRIRrules() already had a corresponding check, thereby preventing RLS expansion on EXCLUDED. Also add tests for these issues, and improve a few comments around excluded handling in setrefs.c. Reported-By: Peter Geoghegan, Geoff Winkless Author: Andres Freund, Amit Langote, Peter Geoghegan Discussion: CAEzk6fdzJ3xYQZGbcuYM2rBd2BuDkUksmK=mY9UYYDugg_GgZg@mail.gmail.com, CAM3SWZS+CauzbiCEcg-GdE6K6ycHE_Bz6Ksszy8AoixcMHOmsA@mail.gmail.com Backpatch: 9.5, where ON CONFLICT was introduced
This commit is contained in:
@ -891,33 +891,81 @@ transformOnConflictClause(ParseState *pstate,
|
||||
/* Process DO UPDATE */
|
||||
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
|
||||
* existing SET statements that need to be processed like an UPDATE.
|
||||
*/
|
||||
pstate->p_is_insert = false;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
exclRte = addRangeTableEntryForRelation(pstate,
|
||||
pstate->p_target_relation,
|
||||
targetrel,
|
||||
makeAlias("excluded", NIL),
|
||||
false, false);
|
||||
exclRte->relkind = RELKIND_COMPOSITE_TYPE;
|
||||
exclRelIndex = list_length(pstate->p_rtable);
|
||||
|
||||
/*
|
||||
* Build a targetlist for the EXCLUDED pseudo relation. Out of
|
||||
* simplicity we do that here, because expandRelAttrs() happens to
|
||||
* nearly do the right thing; specifically it also works with views.
|
||||
* It'd be more proper to instead scan some pseudo scan node, but it
|
||||
* doesn't seem worth the amount of code required.
|
||||
*
|
||||
* The only caveat of this hack is that the permissions expandRelAttrs
|
||||
* adds have to be reset. markVarForSelectPriv() will add the exact
|
||||
* required permissions back.
|
||||
* Build a targetlist for the EXCLUDED pseudo relation. Have to be
|
||||
* careful to use resnos that correspond to attnos of the underlying
|
||||
* relation.
|
||||
*/
|
||||
exclRelTlist = expandRelAttrs(pstate, exclRte,
|
||||
exclRelIndex, 0, -1);
|
||||
exclRte->requiredPerms = 0;
|
||||
exclRte->selectedCols = NULL;
|
||||
Assert(pstate->p_next_resno == 1);
|
||||
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);
|
||||
var->location = -1;
|
||||
|
||||
name = NameStr(attr->attname);
|
||||
}
|
||||
|
||||
Assert(pstate->p_next_resno == attno + 1);
|
||||
te = makeTargetEntry((Expr *) var,
|
||||
pstate->p_next_resno++,
|
||||
name,
|
||||
false);
|
||||
|
||||
/* don't require select access yet */
|
||||
exclRelTlist = lappend(exclRelTlist, te);
|
||||
}
|
||||
|
||||
/*
|
||||
* Additionally add a whole row tlist entry for EXCLUDED. That's
|
||||
* really only needed for ruleutils' benefit, which expects to find
|
||||
* corresponding entries in child tlists. Alternatively we could do
|
||||
* this only when required, but that doesn't seem worth the trouble.
|
||||
*/
|
||||
var = makeVar(exclRelIndex, InvalidAttrNumber,
|
||||
RelationGetRelid(targetrel),
|
||||
-1, InvalidOid, 0);
|
||||
te = makeTargetEntry((Expr *) var, 0, NULL, true);
|
||||
exclRelTlist = lappend(exclRelTlist, te);
|
||||
|
||||
/*
|
||||
* Add EXCLUDED and the target RTE to the namespace, so that they can
|
||||
|
@ -686,9 +686,12 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
|
||||
return result;
|
||||
|
||||
/*
|
||||
* If the RTE represents a real table, consider system column names.
|
||||
* If the RTE represents a real relation, consider system column names.
|
||||
* Composites are only used for pseudo-relations like ON CONFLICT's
|
||||
* excluded.
|
||||
*/
|
||||
if (rte->rtekind == RTE_RELATION)
|
||||
if (rte->rtekind == RTE_RELATION &&
|
||||
rte->relkind != RELKIND_COMPOSITE_TYPE)
|
||||
{
|
||||
/* quick check to see if name could be a system column */
|
||||
attnum = specialAttNum(colname);
|
||||
|
Reference in New Issue
Block a user