Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5dcdd77

Browse files
deanrasheedCommitfest Bot
authored and
Commitfest Bot
committed
Review comments for ON CONFLICT DO SELECT.
1 parent 9c41015 commit 5dcdd77

File tree

13 files changed

+336
-161
lines changed

13 files changed

+336
-161
lines changed

doc/src/sgml/ref/create_policy.sgml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,22 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
491491
<entry>New row</entry>
492492
<entry>&mdash;</entry>
493493
</row>
494+
<row>
495+
<entry><command>ON CONFLICT DO SELECT</command></entry>
496+
<entry>Existing &amp; new rows</entry>
497+
<entry>&mdash;</entry>
498+
<entry>&mdash;</entry>
499+
<entry>&mdash;</entry>
500+
<entry>&mdash;</entry>
501+
</row>
502+
<row>
503+
<entry><command>ON CONFLICT DO SELECT FOR UPDATE/SHARE</command></entry>
504+
<entry>Existing &amp; new rows</entry>
505+
<entry>&mdash;</entry>
506+
<entry>Existing row</entry>
507+
<entry>&mdash;</entry>
508+
<entry>&mdash;</entry>
509+
</row>
494510
</tbody>
495511
</tgroup>
496512
</table>

src/backend/commands/explain.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4651,14 +4651,15 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
46514651

46524652
if (node->onConflictAction != ONCONFLICT_NONE)
46534653
{
4654-
const char *resolution;
4654+
const char *resolution = NULL;
46554655

46564656
if (node->onConflictAction == ONCONFLICT_NOTHING)
46574657
resolution = "NOTHING";
46584658
else if (node->onConflictAction == ONCONFLICT_UPDATE)
46594659
resolution = "UPDATE";
46604660
else
46614661
{
4662+
Assert(node->onConflictAction == ONCONFLICT_SELECT);
46624663
switch (node->onConflictLockingStrength)
46634664
{
46644665
case LCS_NONE:
@@ -4673,9 +4674,13 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
46734674
case LCS_FORNOKEYUPDATE:
46744675
resolution = "SELECT FOR NO KEY UPDATE";
46754676
break;
4676-
default: /* LCS_FORUPDATE */
4677+
case LCS_FORUPDATE:
46774678
resolution = "SELECT FOR UPDATE";
46784679
break;
4680+
default:
4681+
elog(ERROR, "unrecognized LockClauseStrength %d",
4682+
(int) node->onConflictLockingStrength);
4683+
break;
46794684
}
46804685
}
46814686

@@ -4688,7 +4693,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
46884693
if (idxNames)
46894694
ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
46904695

4691-
/* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
4696+
/* ON CONFLICT DO UPDATE/SELECT WHERE qual is specially displayed */
46924697
if (node->onConflictWhere)
46934698
{
46944699
show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",

src/backend/executor/nodeModifyTable.c

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ static bool ExecOnConflictUpdate(ModifyTableContext *context,
160160
static bool ExecOnConflictSelect(ModifyTableContext *context,
161161
ResultRelInfo *resultRelInfo,
162162
ItemPointer conflictTid,
163+
TupleTableSlot *excludedSlot,
163164
bool canSetTag,
164165
TupleTableSlot **returning);
165166
static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
@@ -1154,13 +1155,13 @@ ExecInsert(ModifyTableContext *context,
11541155
/*
11551156
* In case of ON CONFLICT DO SELECT, optionally lock the
11561157
* conflicting tuple, fetch it and project RETURNING on
1157-
* it. Be prepared to retry if fetching fails because of a
1158+
* it. Be prepared to retry if locking fails because of a
11581159
* concurrent UPDATE/DELETE to the conflict tuple.
11591160
*/
11601161
TupleTableSlot *returning = NULL;
11611162

11621163
if (ExecOnConflictSelect(context, resultRelInfo,
1163-
&conflictTid, canSetTag,
1164+
&conflictTid, slot, canSetTag,
11641165
&returning))
11651166
{
11661167
InstrCountTuples2(&mtstate->ps, 1);
@@ -2708,6 +2709,12 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
27082709

27092710
/*
27102711
* ExecOnConflictLockRow --- lock the row for ON CONFLICT DO UPDATE/SELECT
2712+
*
2713+
* Try to lock tuple for update as part of speculative insertion for ON
2714+
* CONFLICT DO UPDATE or ON CONFLICT DO SELECT FOR UPDATE/SHARE.
2715+
*
2716+
* Returns true if the row is successfully locked, or false if the caller must
2717+
* retry the INSERT from scratch.
27112718
*/
27122719
static bool
27132720
ExecOnConflictLockRow(ModifyTableContext *context,
@@ -2865,7 +2872,7 @@ ExecOnConflictUpdate(ModifyTableContext *context,
28652872
/* Determine lock mode to use */
28662873
lockmode = ExecUpdateLockMode(context->estate, resultRelInfo);
28672874

2868-
/* Lock tuple for update. */
2875+
/* Lock tuple for update */
28692876
if (!ExecOnConflictLockRow(context, existing, conflictTid,
28702877
resultRelInfo->ri_RelationDesc, lockmode, true))
28712878
return false;
@@ -2910,11 +2917,12 @@ ExecOnConflictUpdate(ModifyTableContext *context,
29102917
* security barrier quals (if any), enforced here as RLS checks/WCOs.
29112918
*
29122919
* The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
2913-
* quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK,
2914-
* but that's almost the extent of its special handling for ON
2915-
* CONFLICT DO UPDATE.
2920+
* quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK. If
2921+
* SELECT rights are required on the target table, the rewriter also
2922+
* adds SELECT RLS checks/WCOs for SELECT security quals, using WCOs
2923+
* of the same kind, so this check enforces them too.
29162924
*
2917-
* The rewriter will also have associated UPDATE applicable straight
2925+
* The rewriter will also have associated UPDATE-applicable straight
29182926
* RLS checks/WCOs for the benefit of the ExecUpdate() call that
29192927
* follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
29202928
* kinds, so there is no danger of spurious over-enforcement in the
@@ -2962,13 +2970,18 @@ ExecOnConflictUpdate(ModifyTableContext *context,
29622970
/*
29632971
* ExecOnConflictSelect --- execute SELECT of INSERT ON CONFLICT DO SELECT
29642972
*
2965-
* Returns true if if we're done (with or without an update), or false if the
2973+
* If SELECT FOR UPDATE/SHARE is specified, try to lock tuple as part of
2974+
* speculative insertion. If a qual originating from ON CONFLICT DO UPDATE is
2975+
* satisfied, select the row.
2976+
*
2977+
* Returns true if if we're done (with or without a select), or false if the
29662978
* caller must retry the INSERT from scratch.
29672979
*/
29682980
static bool
29692981
ExecOnConflictSelect(ModifyTableContext *context,
29702982
ResultRelInfo *resultRelInfo,
29712983
ItemPointer conflictTid,
2984+
TupleTableSlot *excludedSlot,
29722985
bool canSetTag,
29732986
TupleTableSlot **rslot)
29742987
{
@@ -3026,11 +3039,13 @@ ExecOnConflictSelect(ModifyTableContext *context,
30263039
ExecCheckTupleVisible(context->estate, relation, existing);
30273040

30283041
/*
3029-
* Make the tuple available to ExecQual and ExecProject. EXCLUDED is not
3030-
* used at all.
3042+
* Make tuple and any needed join variables available to ExecQual. The
3043+
* EXCLUDED tuple is installed in ecxt_innertuple, while the target's
3044+
* existing tuple is installed in the scantuple. EXCLUDED has been made
3045+
* to reference INNER_VAR in setrefs.c, but there is no other redirection.
30313046
*/
30323047
econtext->ecxt_scantuple = existing;
3033-
econtext->ecxt_innertuple = NULL;
3048+
econtext->ecxt_innertuple = excludedSlot;
30343049
econtext->ecxt_outertuple = NULL;
30353050

30363051
if (!ExecQual(onConflictSelectWhere, econtext))
@@ -3043,19 +3058,15 @@ ExecOnConflictSelect(ModifyTableContext *context,
30433058
if (resultRelInfo->ri_WithCheckOptions != NIL)
30443059
{
30453060
/*
3046-
* Check target's existing tuple against UPDATE-applicable USING
3061+
* Check target's existing tuple against SELECT-applicable USING
30473062
* security barrier quals (if any), enforced here as RLS checks/WCOs.
30483063
*
3049-
* The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
3050-
* quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK,
3051-
* but that's almost the extent of its special handling for ON
3052-
* CONFLICT DO UPDATE.
3053-
*
3054-
* The rewriter will also have associated UPDATE applicable straight
3055-
* RLS checks/WCOs for the benefit of the ExecUpdate() call that
3056-
* follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
3057-
* kinds, so there is no danger of spurious over-enforcement in the
3058-
* INSERT or UPDATE path.
3064+
* The rewriter creates SELECT RLS checks/WCOs for SELECT security
3065+
* quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK. If
3066+
* FOR UPDATE/SHARE was specified, UPDATE rights are required on the
3067+
* target table, and the rewriter also adds UPDATE RLS checks/WCOs for
3068+
* UPDATE security quals, using WCOs of the same kind, so this check
3069+
* enforces them too.
30593070
*/
30603071
ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
30613072
existing,
@@ -3065,8 +3076,9 @@ ExecOnConflictSelect(ModifyTableContext *context,
30653076
/* Parse analysis should already have disallowed this */
30663077
Assert(resultRelInfo->ri_projectReturning);
30673078

3068-
*rslot = ExecProcessReturning(context, resultRelInfo, CMD_INSERT,
3069-
existing, NULL, context->planSlot);
3079+
/* Process RETURNING like an UPDATE that didn't change anything */
3080+
*rslot = ExecProcessReturning(context, resultRelInfo, CMD_UPDATE,
3081+
existing, existing, context->planSlot);
30703082

30713083
if (canSetTag)
30723084
context->estate->es_processed++;
@@ -3083,6 +3095,7 @@ ExecOnConflictSelect(ModifyTableContext *context,
30833095
* query.
30843096
*/
30853097
ExecClearTuple(existing);
3098+
30863099
return true;
30873100
}
30883101

src/backend/optimizer/plan/setrefs.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
11141114
* those are already used by RETURNING and it seems better to
11151115
* be non-conflicting.
11161116
*/
1117-
if (splan->onConflictSet)
1117+
if (splan->onConflictAction == ONCONFLICT_UPDATE ||
1118+
splan->onConflictAction == ONCONFLICT_SELECT)
11181119
{
11191120
indexed_tlist *itlist;
11201121

src/backend/parser/analyze.c

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,11 +1102,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
11021102
false, true, true);
11031103
}
11041104

1105-
if (stmt->onConflictClause && stmt->onConflictClause->action == ONCONFLICT_SELECT && !stmt->returningClause)
1105+
/* ON CONFLICT DO SELECT requires a RETURNING clause */
1106+
if (stmt->onConflictClause &&
1107+
stmt->onConflictClause->action == ONCONFLICT_SELECT &&
1108+
!stmt->returningClause)
11061109
ereport(ERROR,
1107-
(errcode(ERRCODE_SYNTAX_ERROR),
1108-
errmsg("ON CONFLICT DO SELECT requires a RETURNING clause"),
1109-
parser_errposition(pstate, stmt->onConflictClause->location)));
1110+
errcode(ERRCODE_SYNTAX_ERROR),
1111+
errmsg("ON CONFLICT DO SELECT requires a RETURNING clause"),
1112+
parser_errposition(pstate, stmt->onConflictClause->location));
11101113

11111114
/* Process ON CONFLICT, if any. */
11121115
if (stmt->onConflictClause)
@@ -1266,12 +1269,13 @@ transformOnConflictClause(ParseState *pstate,
12661269
OnConflictExpr *result;
12671270

12681271
/*
1269-
* If this is ON CONFLICT ... UPDATE, first create the range table entry
1270-
* for the EXCLUDED pseudo relation, so that that will be present while
1271-
* processing arbiter expressions. (You can't actually reference it from
1272-
* there, but this provides a useful error message if you try.)
1272+
* If this is ON CONFLICT ... UPDATE/SELECT, first create the range table
1273+
* entry for the EXCLUDED pseudo relation, so that that will be present
1274+
* while processing arbiter expressions. (You can't actually reference it
1275+
* from there, but this provides a useful error message if you try.)
12731276
*/
1274-
if (onConflictClause->action == ONCONFLICT_UPDATE)
1277+
if (onConflictClause->action == ONCONFLICT_UPDATE ||
1278+
onConflictClause->action == ONCONFLICT_SELECT)
12751279
{
12761280
Relation targetrel = pstate->p_target_relation;
12771281
RangeTblEntry *exclRte;
@@ -1300,27 +1304,28 @@ transformOnConflictClause(ParseState *pstate,
13001304
transformOnConflictArbiter(pstate, onConflictClause, &arbiterElems,
13011305
&arbiterWhere, &arbiterConstraint);
13021306

1303-
/* Process DO UPDATE */
1304-
if (onConflictClause->action == ONCONFLICT_UPDATE)
1307+
/* Process DO UPDATE/SELECT */
1308+
if (onConflictClause->action == ONCONFLICT_UPDATE ||
1309+
onConflictClause->action == ONCONFLICT_SELECT)
13051310
{
1306-
/*
1307-
* Expressions in the UPDATE targetlist need to be handled like UPDATE
1308-
* not INSERT. We don't need to save/restore this because all INSERT
1309-
* expressions have been parsed already.
1310-
*/
1311-
pstate->p_is_insert = false;
1312-
13131311
/*
13141312
* Add the EXCLUDED pseudo relation to the query namespace, making it
1315-
* available in the UPDATE subexpressions.
1313+
* available in the UPDATE/SELECT subexpressions.
13161314
*/
13171315
addNSItemToQuery(pstate, exclNSItem, false, true, true);
13181316

1319-
/*
1320-
* Now transform the UPDATE subexpressions.
1321-
*/
1322-
onConflictSet =
1323-
transformUpdateTargetList(pstate, onConflictClause->targetList);
1317+
if (onConflictClause->action == ONCONFLICT_UPDATE)
1318+
{
1319+
/*
1320+
* Expressions in the UPDATE targetlist need to be handled like
1321+
* UPDATE not INSERT. We don't need to save/restore this because
1322+
* all INSERT expressions have been parsed already.
1323+
*/
1324+
pstate->p_is_insert = false;
1325+
1326+
onConflictSet =
1327+
transformUpdateTargetList(pstate, onConflictClause->targetList);
1328+
}
13241329

13251330
onConflictWhere = transformWhereClause(pstate,
13261331
onConflictClause->whereClause,
@@ -1334,13 +1339,6 @@ transformOnConflictClause(ParseState *pstate,
13341339
Assert((ParseNamespaceItem *) llast(pstate->p_namespace) == exclNSItem);
13351340
pstate->p_namespace = list_delete_last(pstate->p_namespace);
13361341
}
1337-
else if (onConflictClause->action == ONCONFLICT_SELECT)
1338-
{
1339-
onConflictWhere = transformWhereClause(pstate,
1340-
onConflictClause->whereClause,
1341-
EXPR_KIND_WHERE, "WHERE");
1342-
1343-
}
13441342

13451343
/* Finally, build ON CONFLICT DO [NOTHING | SELECT | UPDATE] expression */
13461344
result = makeNode(OnConflictExpr);

src/backend/rewrite/rewriteHandler.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,19 @@ rewriteRuleAction(Query *parsetree,
655655
rule_action = sub_action;
656656
}
657657

658+
/*
659+
* If rule_action is INSERT .. ON CONFLICT DO SELECT, the parser should
660+
* have verified that it has a RETURNING clause, but we must also check
661+
* that the triggering query has a RETURNING clause.
662+
*/
663+
if (rule_action->onConflict &&
664+
rule_action->onConflict->action == ONCONFLICT_SELECT &&
665+
(!rule_action->returningList || !parsetree->returningList))
666+
ereport(ERROR,
667+
errcode(ERRCODE_SYNTAX_ERROR),
668+
errmsg("ON CONFLICT DO SELECT requires a RETURNING clause"),
669+
errdetail("A rule action is INSERT ... ON CONFLICT DO SELECT, which requires a RETURNING clause"));
670+
658671
/*
659672
* If rule_action has a RETURNING clause, then either throw it away if the
660673
* triggering query has no RETURNING clause, or rewrite it to emit what

0 commit comments

Comments
 (0)