Skip to content

Commit f2afca6

Browse files
deanrasheedCommitfest Bot
authored andcommitted
Allow EXCLUDED in RETURNING list of INSERT ON CONFLICT DO UPDATE.
This allows an INSERT ... ON CONFLICT DO UPDATE command to return the excluded values, when conflicts occur. For rows that do not conflict, the excluded values returned are NULL. This builds on the infrastructure added to support OLD/NEW values in the RETURNING list, except that in the case of EXCLUDED, there is no need for the special syntax to change the name of the excluded pseudo-relation in the RETURNING list. (That was required for OLD/NEW, because those names were already used in some situations, such as trigger functions, but there is no such problem with excluded.) Discussion: https://postgr.es/m/CAEZATCXXu2ohYmn=4YrRQa9yNwD_fdEEOTBPgM_5jhQOFcaQ4g@mail.gmail.com
1 parent 317c117 commit f2afca6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+741
-269
lines changed

doc/src/sgml/dml.sgml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,19 @@ UPDATE products SET price = price * 1.10
392392
the new values may be non-<literal>NULL</literal>.
393393
</para>
394394

395+
<para>
396+
In an <command>INSERT</command> with an <literal>ON CONFLICT DO UPDATE</literal>
397+
clause, it is also possible to return the values excluded, if there is a
398+
conflict. For example:
399+
<programlisting>
400+
INSERT INTO products AS p SELECT * FROM new_products
401+
ON CONFLICT (product_no) DO UPDATE SET name = excluded.name
402+
RETURNING p.product_no, p.name, excluded.price;
403+
</programlisting>
404+
Excluded values will be <literal>NULL</literal> for rows that do not
405+
conflict.
406+
</para>
407+
395408
<para>
396409
If there are triggers (<xref linkend="triggers"/>) on the target table,
397410
the data available to <literal>RETURNING</literal> is the row as modified by

doc/src/sgml/ref/insert.sgml

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
183183
This is particularly useful when <literal>ON CONFLICT DO UPDATE</literal>
184184
targets a table named <varname>excluded</varname>, since that will otherwise
185185
be taken as the name of the special table representing the row proposed
186-
for insertion.
186+
for insertion (see note below).
187187
</para>
188188
</listitem>
189189
</varlistentry>
@@ -343,6 +343,35 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
343343
with an <literal>ON CONFLICT DO UPDATE</literal> clause, the old
344344
values may be non-<literal>NULL</literal>.
345345
</para>
346+
347+
<para>
348+
If the <command>INSERT</command> has an <literal>ON CONFLICT DO
349+
UPDATE</literal> clause, a column name or <literal>*</literal> may be
350+
qualified using <literal>EXCLUDED</literal> to return the values
351+
excluded from insertion, in the event of a conflict. If there is no
352+
conflict, then all excluded values will be <literal>NULL</literal>.
353+
</para>
354+
355+
<note>
356+
<para>
357+
The special <varname>excluded</varname> table is made available
358+
whenever an <command>INSERT</command> has an <literal>ON CONFLICT DO
359+
UPDATE</literal> clause. It has the same columns as the target
360+
table, and in the event of a conflict, it is populated with the
361+
values that would have been inserted. This includes any values that
362+
were supplied by defaults, as well as the effects of any per-row
363+
<literal>BEFORE INSERT</literal> triggers, since those may have
364+
contributed to the row being excluded from insertion.
365+
</para>
366+
<para>
367+
The <varname>excluded</varname> table is accessible from the
368+
<literal>SET</literal> and <literal>WHERE</literal> clauses of the
369+
<literal>ON CONFLICT DO UPDATE</literal> action, and the
370+
<literal>RETURNING</literal> list. <literal>SELECT</literal>
371+
privilege is required on any columns in the target table where the
372+
corresponding <varname>excluded</varname> columns are read.
373+
</para>
374+
</note>
346375
</listitem>
347376
</varlistentry>
348377

@@ -440,16 +469,7 @@ INSERT INTO <replaceable class="parameter">table_name</replaceable> [ AS <replac
440469
<literal>WHERE</literal> clauses in <literal>ON CONFLICT DO
441470
UPDATE</literal> have access to the existing row using the
442471
table's name (or an alias), and to the row proposed for insertion
443-
using the special <varname>excluded</varname> table.
444-
<literal>SELECT</literal> privilege is required on any column in the
445-
target table where corresponding <varname>excluded</varname>
446-
columns are read.
447-
</para>
448-
<para>
449-
Note that the effects of all per-row <literal>BEFORE
450-
INSERT</literal> triggers are reflected in
451-
<varname>excluded</varname> values, since those effects may
452-
have contributed to the row being excluded from insertion.
472+
using the special <varname>excluded</varname> table (see note above).
453473
</para>
454474
</listitem>
455475
</varlistentry>

src/backend/executor/execExpr.c

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ ExecBuildProjectionInfo(List *targetList,
455455
/*
456456
* Get the tuple from the relation being scanned, or the
457457
* old/new tuple slot, if old/new values were requested.
458+
* Should not see EXCLUDED here (should be INNER_VAR).
458459
*/
459460
switch (variable->varreturningtype)
460461
{
@@ -469,6 +470,10 @@ ExecBuildProjectionInfo(List *targetList,
469470
scratch.opcode = EEOP_ASSIGN_NEW_VAR;
470471
state->flags |= EEO_FLAG_HAS_NEW;
471472
break;
473+
case VAR_RETURNING_EXCLUDED:
474+
elog(ERROR, "wrong varno %d (expected %d) for variable returning excluded",
475+
variable->varno, INNER_VAR);
476+
break;
472477
}
473478
break;
474479
}
@@ -972,6 +977,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
972977
scratch.opcode = EEOP_NEW_SYSVAR;
973978
state->flags |= EEO_FLAG_HAS_NEW;
974979
break;
980+
case VAR_RETURNING_EXCLUDED:
981+
elog(ERROR, "wrong varno %d (expected %d) for variable returning excluded",
982+
variable->varno, INNER_VAR);
983+
break;
975984
}
976985
break;
977986
}
@@ -1007,6 +1016,10 @@ ExecInitExprRec(Expr *node, ExprState *state,
10071016
scratch.opcode = EEOP_NEW_VAR;
10081017
state->flags |= EEO_FLAG_HAS_NEW;
10091018
break;
1019+
case VAR_RETURNING_EXCLUDED:
1020+
elog(ERROR, "wrong varno %d (expected %d) for variable returning excluded",
1021+
variable->varno, INNER_VAR);
1022+
break;
10101023
}
10111024
break;
10121025
}
@@ -2638,25 +2651,39 @@ ExecInitExprRec(Expr *node, ExprState *state,
26382651
ReturningExpr *rexpr = (ReturningExpr *) node;
26392652
int retstep;
26402653

2641-
/* Skip expression evaluation if OLD/NEW row doesn't exist */
2654+
/*
2655+
* Skip expression evaluation if OLD/NEW/EXCLUDED row doesn't
2656+
* exist.
2657+
*/
26422658
scratch.opcode = EEOP_RETURNINGEXPR;
2643-
scratch.d.returningexpr.nullflag = rexpr->retold ?
2644-
EEO_FLAG_OLD_IS_NULL : EEO_FLAG_NEW_IS_NULL;
2659+
switch (rexpr->retkind)
2660+
{
2661+
case RETURNING_OLD_EXPR:
2662+
scratch.d.returningexpr.nullflag = EEO_FLAG_OLD_IS_NULL;
2663+
break;
2664+
case RETURNING_NEW_EXPR:
2665+
scratch.d.returningexpr.nullflag = EEO_FLAG_NEW_IS_NULL;
2666+
break;
2667+
case RETURNING_EXCLUDED_EXPR:
2668+
scratch.d.returningexpr.nullflag = EEO_FLAG_INNER_IS_NULL;
2669+
break;
2670+
}
26452671
scratch.d.returningexpr.jumpdone = -1; /* set below */
26462672
ExprEvalPushStep(state, &scratch);
26472673
retstep = state->steps_len - 1;
26482674

26492675
/* Steps to evaluate expression to return */
26502676
ExecInitExprRec(rexpr->retexpr, state, resv, resnull);
26512677

2652-
/* Jump target used if OLD/NEW row doesn't exist */
2678+
/* Jump target used if OLD/NEW/EXCLUDED row doesn't exist */
26532679
state->steps[retstep].d.returningexpr.jumpdone = state->steps_len;
26542680

26552681
/* Update ExprState flags */
2656-
if (rexpr->retold)
2682+
if (rexpr->retkind == RETURNING_OLD_EXPR)
26572683
state->flags |= EEO_FLAG_HAS_OLD;
2658-
else
2684+
else if (rexpr->retkind == RETURNING_NEW_EXPR)
26592685
state->flags |= EEO_FLAG_HAS_NEW;
2686+
/* we don't bother recording references to EXCLUDED */
26602687

26612688
break;
26622689
}
@@ -3013,6 +3040,10 @@ expr_setup_walker(Node *node, ExprSetupInfo *info)
30133040
case VAR_RETURNING_NEW:
30143041
info->last_new = Max(info->last_new, attnum);
30153042
break;
3043+
case VAR_RETURNING_EXCLUDED:
3044+
elog(ERROR, "wrong varno %d (expected %d) for variable returning excluded",
3045+
variable->varno, INNER_VAR);
3046+
break;
30163047
}
30173048
break;
30183049
}
@@ -3178,6 +3209,7 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
31783209
state->flags |= EEO_FLAG_HAS_OLD;
31793210
else if (variable->varreturningtype == VAR_RETURNING_NEW)
31803211
state->flags |= EEO_FLAG_HAS_NEW;
3212+
/* we don't bother recording references to EXCLUDED */
31813213

31823214
/*
31833215
* If the input tuple came from a subquery, it might contain "resjunk"

src/backend/executor/execExprInterp.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5339,7 +5339,21 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
53395339
switch (variable->varno)
53405340
{
53415341
case INNER_VAR:
5342-
/* get the tuple from the inner node */
5342+
5343+
/*
5344+
* Get the tuple from the inner node.
5345+
*
5346+
* In a RETURNING expression, this is used for EXCLUDED values in
5347+
* an INSERT ... ON CONFLICT DO UPDATE. If the non-conflicting
5348+
* branch is taken, the EXCLUDED row is NULL, and we return NULL
5349+
* rather than an all-NULL record.
5350+
*/
5351+
if (state->flags & EEO_FLAG_INNER_IS_NULL)
5352+
{
5353+
*op->resvalue = (Datum) 0;
5354+
*op->resnull = true;
5355+
return;
5356+
}
53435357
slot = econtext->ecxt_innertuple;
53445358
break;
53455359

@@ -5384,6 +5398,11 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
53845398
}
53855399
slot = econtext->ecxt_newtuple;
53865400
break;
5401+
5402+
case VAR_RETURNING_EXCLUDED:
5403+
elog(ERROR, "wrong varno %d (expected %d) for variable returning excluded",
5404+
variable->varno, INNER_VAR);
5405+
break;
53875406
}
53885407
break;
53895408
}

src/backend/executor/execPartition.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,12 +642,26 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
642642

643643
/*
644644
* Convert Vars in it to contain this partition's attribute numbers.
645+
* If we're doing an INSERT ... ON CONFLICT DO UPDATE, the RETURNING
646+
* list might contain references to the EXCLUDED pseudo-relation
647+
* (INNER_VAR), so we must map their attribute numbers too.
645648
*/
646649
if (part_attmap == NULL)
647650
part_attmap =
648651
build_attrmap_by_name(RelationGetDescr(partrel),
649652
RelationGetDescr(firstResultRel),
650653
false);
654+
655+
if (node->onConflictAction == ONCONFLICT_UPDATE)
656+
{
657+
returningList = (List *)
658+
map_variable_attnos((Node *) returningList,
659+
INNER_VAR, 0,
660+
part_attmap,
661+
RelationGetForm(partrel)->reltype,
662+
&found_whole_row);
663+
/* We ignore the value of found_whole_row. */
664+
}
651665
returningList = (List *)
652666
map_variable_attnos((Node *) returningList,
653667
firstVarno, 0,

0 commit comments

Comments
 (0)