v14-0002-More-suggested-review-comments.patch
application/octet-stream
Filename: v14-0002-More-suggested-review-comments.patch
Type: application/octet-stream
Part: 1
Message:
Re: ON CONFLICT DO SELECT (take 3)
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v14-0002
Subject: More suggested review comments.
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/postgres_fdw.c | 1 | 1 |
| src/backend/commands/explain.c | 1 | 5 |
| src/backend/executor/execPartition.c | 77 | 127 |
| src/backend/executor/nodeModifyTable.c | 40 | 56 |
| src/backend/optimizer/plan/createplan.c | 5 | 3 |
| src/backend/optimizer/util/plancat.c | 8 | 6 |
| src/backend/parser/analyze.c | 6 | 2 |
| src/backend/parser/gram.y | 3 | 3 |
| src/backend/parser/parse_clause.c | 8 | 13 |
| src/backend/rewrite/rewriteHandler.c | 13 | 16 |
| src/backend/utils/adt/ruleutils.c | 2 | 2 |
| src/include/nodes/execnodes.h | 2 | 3 |
| src/include/nodes/parsenodes.h | 4 | 5 |
| src/include/nodes/plannodes.h | 1 | 1 |
| src/include/nodes/primnodes.h | 7 | 8 |
| src/test/regress/expected/rules.out | 1 | 1 |
From 8432d99ce4612b74db2c55852ba7421ae550a2ae Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Wed, 19 Nov 2025 13:45:12 +0000
Subject: [PATCH v14 2/4] More suggested review comments.
- Fix a few code comments.
- Reduce code duplication in executor.
- Rename "lockingStrength" to "lockStrength" (feels slightly better to me).
- Remove unneeded "if" from rewriteTargetView() (should reduce final patch size).
---
contrib/postgres_fdw/postgres_fdw.c | 2 +-
src/backend/commands/explain.c | 6 +-
src/backend/executor/execPartition.c | 204 +++++++++---------------
src/backend/executor/nodeModifyTable.c | 96 +++++------
src/backend/optimizer/plan/createplan.c | 8 +-
src/backend/optimizer/util/plancat.c | 14 +-
src/backend/parser/analyze.c | 8 +-
src/backend/parser/gram.y | 6 +-
src/backend/parser/parse_clause.c | 21 +--
src/backend/rewrite/rewriteHandler.c | 29 ++--
src/backend/utils/adt/ruleutils.c | 4 +-
src/include/nodes/execnodes.h | 5 +-
src/include/nodes/parsenodes.h | 9 +-
src/include/nodes/plannodes.h | 2 +-
src/include/nodes/primnodes.h | 15 +-
src/test/regress/expected/rules.out | 2 +-
16 files changed, 179 insertions(+), 252 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 06b52c65300..b793669d97f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1856,7 +1856,7 @@ postgresPlanForeignModify(PlannerInfo *root,
returningList = (List *) list_nth(plan->returningLists, subplan_index);
/*
- * ON CONFLICT DO UPDATE and DO NOTHING case with inference specification
+ * ON CONFLICT DO NOTHING/UPDATE/SELECT with inference specification
* should have already been rejected in the optimizer, as presently there
* is no way to recognize an arbiter index on a foreign table. Only DO
* NOTHING is supported without an inference specification.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1a575cc96e8..10e636d465e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4679,7 +4679,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
else
{
Assert(node->onConflictAction == ONCONFLICT_SELECT);
- switch (node->onConflictLockingStrength)
+ switch (node->onConflictLockStrength)
{
case LCS_NONE:
resolution = "SELECT";
@@ -4696,10 +4696,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
case LCS_FORUPDATE:
resolution = "SELECT FOR UPDATE";
break;
- default:
- elog(ERROR, "unrecognized LockClauseStrength %d",
- (int) node->onConflictLockingStrength);
- break;
}
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index a8f7d1dc5bd..0868229328b 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -731,20 +731,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
leaf_part_rri->ri_onConflictArbiterIndexes = arbiterIndexes;
/*
- * In the DO UPDATE case, we have some more state to initialize.
+ * In the DO UPDATE and DO SELECT cases, we have some more state to
+ * initialize.
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
+ if (node->onConflictAction == ONCONFLICT_UPDATE ||
+ node->onConflictAction == ONCONFLICT_SELECT)
{
OnConflictActionState *onconfl = makeNode(OnConflictActionState);
TupleConversionMap *map;
map = ExecGetRootToChildMap(leaf_part_rri, estate);
- Assert(node->onConflictSet != NIL);
+ Assert(node->onConflictSet != NIL ||
+ node->onConflictAction == ONCONFLICT_SELECT);
Assert(rootResultRelInfo->ri_onConflict != NULL);
leaf_part_rri->ri_onConflict = onconfl;
+ /* Lock strength for DO SELECT [FOR UPDATE/SHARE] */
+ onconfl->oc_LockStrength =
+ rootResultRelInfo->ri_onConflict->oc_LockStrength;
+
/*
* Need a separate existing slot for each partition, as the
* partition could be of a different AM, even if the tuple
@@ -757,7 +764,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/*
* If the partition's tuple descriptor matches exactly the root
* parent (the common case), we can re-use most of the parent's ON
- * CONFLICT SET state, skipping a bunch of work. Otherwise, we
+ * CONFLICT action state, skipping a bunch of work. Otherwise, we
* need to create state specific to this partition.
*/
if (map == NULL)
@@ -765,7 +772,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/*
* It's safe to reuse these from the partition root, as we
* only process one tuple at a time (therefore we won't
- * overwrite needed data in slots), and the results of
+ * overwrite needed data in slots), and the results of any
* projections are independent of the underlying storage.
* Projections and where clauses themselves don't store state
* / are independent of the underlying storage.
@@ -779,66 +786,81 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
else
{
- List *onconflset;
- List *onconflcols;
-
/*
- * Translate expressions in onConflictSet to account for
- * different attribute numbers. For that, map partition
- * varattnos twice: first to catch the EXCLUDED
- * pseudo-relation (INNER_VAR), and second to handle the main
- * target relation (firstVarno).
+ * For ON CONFLICT DO UPDATE, translate expressions in
+ * onConflictSet to account for different attribute numbers.
+ * For that, map partition varattnos twice: first to catch the
+ * EXCLUDED pseudo-relation (INNER_VAR), and second to handle
+ * the main target relation (firstVarno).
*/
- onconflset = copyObject(node->onConflictSet);
- if (part_attmap == NULL)
- part_attmap =
- build_attrmap_by_name(RelationGetDescr(partrel),
- RelationGetDescr(firstResultRel),
- false);
- onconflset = (List *)
- map_variable_attnos((Node *) onconflset,
- INNER_VAR, 0,
- part_attmap,
- RelationGetForm(partrel)->reltype,
- &found_whole_row);
- /* We ignore the value of found_whole_row. */
- onconflset = (List *)
- map_variable_attnos((Node *) onconflset,
- firstVarno, 0,
- part_attmap,
- RelationGetForm(partrel)->reltype,
- &found_whole_row);
- /* We ignore the value of found_whole_row. */
-
- /* Finally, adjust the target colnos to match the partition. */
- onconflcols = adjust_partition_colnos(node->onConflictCols,
- leaf_part_rri);
-
- /* create the tuple slot for the UPDATE SET projection */
- onconfl->oc_ProjSlot =
- table_slot_create(partrel,
- &mtstate->ps.state->es_tupleTable);
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
+ {
+ List *onconflset;
+ List *onconflcols;
+
+ onconflset = copyObject(node->onConflictSet);
+ if (part_attmap == NULL)
+ part_attmap =
+ build_attrmap_by_name(RelationGetDescr(partrel),
+ RelationGetDescr(firstResultRel),
+ false);
+ onconflset = (List *)
+ map_variable_attnos((Node *) onconflset,
+ INNER_VAR, 0,
+ part_attmap,
+ RelationGetForm(partrel)->reltype,
+ &found_whole_row);
+ /* We ignore the value of found_whole_row. */
+ onconflset = (List *)
+ map_variable_attnos((Node *) onconflset,
+ firstVarno, 0,
+ part_attmap,
+ RelationGetForm(partrel)->reltype,
+ &found_whole_row);
+ /* We ignore the value of found_whole_row. */
- /* build UPDATE SET projection state */
- onconfl->oc_ProjInfo =
- ExecBuildUpdateProjection(onconflset,
- true,
- onconflcols,
- partrelDesc,
- econtext,
- onconfl->oc_ProjSlot,
- &mtstate->ps);
+ /*
+ * Finally, adjust the target colnos to match the
+ * partition.
+ */
+ onconflcols = adjust_partition_colnos(node->onConflictCols,
+ leaf_part_rri);
+
+ /* create the tuple slot for the UPDATE SET projection */
+ onconfl->oc_ProjSlot =
+ table_slot_create(partrel,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* build UPDATE SET projection state */
+ onconfl->oc_ProjInfo =
+ ExecBuildUpdateProjection(onconflset,
+ true,
+ onconflcols,
+ partrelDesc,
+ econtext,
+ onconfl->oc_ProjSlot,
+ &mtstate->ps);
+ }
/*
- * If there is a WHERE clause, initialize state where it will
- * be evaluated, mapping the attribute numbers appropriately.
- * As with onConflictSet, we need to map partition varattnos
- * to the partition's tupdesc.
+ * For both ON CONFLICT DO UPDATE and ON CONFLICT DO SELECT,
+ * there may be a WHERE clause. If so, initialize state where
+ * it will be evaluated, mapping the attribute numbers
+ * appropriately. As with onConflictSet, we need to map
+ * partition varattnos twice, to catch both the EXCLUDED
+ * pseudo-relation (INNER_VAR), and the main target relation
+ * (firstVarno).
*/
if (node->onConflictWhere)
{
List *clause;
+ if (part_attmap == NULL)
+ part_attmap =
+ build_attrmap_by_name(RelationGetDescr(partrel),
+ RelationGetDescr(firstResultRel),
+ false);
+
clause = copyObject((List *) node->onConflictWhere);
clause = (List *)
map_variable_attnos((Node *) clause,
@@ -859,78 +881,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
}
- else if (node->onConflictAction == ONCONFLICT_SELECT)
- {
- OnConflictActionState *onconfl = makeNode(OnConflictActionState);
- TupleConversionMap *map;
-
- map = ExecGetRootToChildMap(leaf_part_rri, estate);
- Assert(rootResultRelInfo->ri_onConflict != NULL);
-
- leaf_part_rri->ri_onConflict = onconfl;
-
- onconfl->oc_LockingStrength =
- rootResultRelInfo->ri_onConflict->oc_LockingStrength;
-
- /*
- * Need a separate existing slot for each partition, as the
- * partition could be of a different AM, even if the tuple
- * descriptors match.
- */
- onconfl->oc_Existing =
- table_slot_create(leaf_part_rri->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /*
- * If the partition's tuple descriptor matches exactly the root
- * parent (the common case), we can re-use the parent's ON
- * CONFLICT DO SELECT state. Otherwise, we need to remap the
- * WHERE clause for this partition's layout.
- */
- if (map == NULL)
- {
- /*
- * It's safe to reuse these from the partition root, as we
- * only process one tuple at a time (therefore we won't
- * overwrite needed data in slots), and the WHERE clause
- * doesn't store state / is independent of the underlying
- * storage.
- */
- onconfl->oc_WhereClause =
- rootResultRelInfo->ri_onConflict->oc_WhereClause;
- }
- else if (node->onConflictWhere)
- {
- /*
- * Map the WHERE clause, if it exists.
- */
- List *clause;
-
- if (part_attmap == NULL)
- part_attmap =
- build_attrmap_by_name(RelationGetDescr(partrel),
- RelationGetDescr(firstResultRel),
- false);
-
- clause = copyObject((List *) node->onConflictWhere);
- clause = (List *)
- map_variable_attnos((Node *) clause,
- INNER_VAR, 0,
- part_attmap,
- RelationGetForm(partrel)->reltype,
- &found_whole_row);
- /* We ignore the value of found_whole_row. */
- clause = (List *)
- map_variable_attnos((Node *) clause,
- firstVarno, 0,
- part_attmap,
- RelationGetForm(partrel)->reltype,
- &found_whole_row);
- /* We ignore the value of found_whole_row. */
- onconfl->oc_WhereClause =
- ExecInitQual(clause, &mtstate->ps);
- }
- }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2939ab32c84..51d0d0a217c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2994,7 +2994,7 @@ ExecOnConflictUpdate(ModifyTableContext *context,
* ExecOnConflictSelect --- execute SELECT of INSERT ON CONFLICT DO SELECT
*
* If SELECT FOR UPDATE/SHARE is specified, try to lock tuple as part of
- * speculative insertion. If a qual originating from ON CONFLICT DO UPDATE is
+ * speculative insertion. If a qual originating from ON CONFLICT DO SELECT is
* satisfied, select the row.
*
* Returns true if we're done (with or without a select), or false if the
@@ -3013,7 +3013,7 @@ ExecOnConflictSelect(ModifyTableContext *context,
Relation relation = resultRelInfo->ri_RelationDesc;
ExprState *onConflictSelectWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
- LockClauseStrength lockstrength = resultRelInfo->ri_onConflict->oc_LockingStrength;
+ LockClauseStrength lockStrength = resultRelInfo->ri_onConflict->oc_LockStrength;
/*
* Parse analysis should have blocked ON CONFLICT for all system
@@ -3023,11 +3023,12 @@ ExecOnConflictSelect(ModifyTableContext *context,
*/
Assert(!resultRelInfo->ri_needLockTagTuple);
- if (lockstrength != LCS_NONE)
+ /* Lock or fetch the existing tuple to select */
+ if (lockStrength != LCS_NONE)
{
LockTupleMode lockmode;
- switch (lockstrength)
+ switch (lockStrength)
{
case LCS_FORKEYSHARE:
lockmode = LockTupleKeyShare;
@@ -3042,7 +3043,7 @@ ExecOnConflictSelect(ModifyTableContext *context,
lockmode = LockTupleExclusive;
break;
default:
- elog(ERROR, "unexpected lock strength %d", lockstrength);
+ elog(ERROR, "unexpected lock strength %d", lockStrength);
}
if (!ExecOnConflictLockRow(context, existing, conflictTid,
@@ -5217,75 +5218,60 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/*
- * If needed, Initialize target list, projection and qual for ON CONFLICT
- * DO UPDATE.
+ * For ON CONFLICT DO UPDATE/SELECT, initialize the ON CONFLICT action
+ * state.
*/
- if (node->onConflictAction == ONCONFLICT_UPDATE)
+ if (node->onConflictAction == ONCONFLICT_UPDATE ||
+ node->onConflictAction == ONCONFLICT_SELECT)
{
OnConflictActionState *onconfl = makeNode(OnConflictActionState);
- ExprContext *econtext;
- TupleDesc relationDesc;
/* already exists if created by RETURNING processing above */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
- relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
- /* create state for DO UPDATE SET operation */
+ /* action state for DO UPDATE/SELECT */
resultRelInfo->ri_onConflict = onconfl;
+ /* lock strength for DO SELECT [FOR UPDATE/SHARE] */
+ onconfl->oc_LockStrength = node->onConflictLockStrength;
+
/* initialize slot for the existing tuple */
onconfl->oc_Existing =
table_slot_create(resultRelInfo->ri_RelationDesc,
&mtstate->ps.state->es_tupleTable);
/*
- * Create the tuple slot for the UPDATE SET projection. We want a slot
- * of the table's type here, because the slot will be used to insert
- * into the table, and for RETURNING processing - which may access
- * system attributes.
+ * For ON CONFLICT DO UPDATE, initialize target list and projection.
*/
- onconfl->oc_ProjSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
-
- /* build UPDATE SET projection state */
- onconfl->oc_ProjInfo =
- ExecBuildUpdateProjection(node->onConflictSet,
- true,
- node->onConflictCols,
- relationDesc,
- econtext,
- onconfl->oc_ProjSlot,
- &mtstate->ps);
-
- /* initialize state to evaluate the WHERE clause, if any */
- if (node->onConflictWhere)
+ if (node->onConflictAction == ONCONFLICT_UPDATE)
{
- ExprState *qualexpr;
+ ExprContext *econtext;
+ TupleDesc relationDesc;
- qualexpr = ExecInitQual((List *) node->onConflictWhere,
- &mtstate->ps);
- onconfl->oc_WhereClause = qualexpr;
- }
- }
- else if (node->onConflictAction == ONCONFLICT_SELECT)
- {
- OnConflictActionState *onconfl = makeNode(OnConflictActionState);
-
- /* already exists if created by RETURNING processing above */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
-
- /* create state for DO SELECT operation */
- resultRelInfo->ri_onConflict = onconfl;
+ econtext = mtstate->ps.ps_ExprContext;
+ relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
- /* initialize slot for the existing tuple */
- onconfl->oc_Existing =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &mtstate->ps.state->es_tupleTable);
+ /*
+ * Create the tuple slot for the UPDATE SET projection. We want a
+ * slot of the table's type here, because the slot will be used to
+ * insert into the table, and for RETURNING processing - which may
+ * access system attributes.
+ */
+ onconfl->oc_ProjSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* build UPDATE SET projection state */
+ onconfl->oc_ProjInfo =
+ ExecBuildUpdateProjection(node->onConflictSet,
+ true,
+ node->onConflictCols,
+ relationDesc,
+ econtext,
+ onconfl->oc_ProjSlot,
+ &mtstate->ps);
+ }
/* initialize state to evaluate the WHERE clause, if any */
if (node->onConflictWhere)
@@ -5296,8 +5282,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
&mtstate->ps);
onconfl->oc_WhereClause = qualexpr;
}
-
- onconfl->oc_LockingStrength = node->onConflictLockingStrength;
}
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52839dbbf2d..8f2586eeda1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -7036,11 +7036,11 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
if (!onconflict)
{
node->onConflictAction = ONCONFLICT_NONE;
+ node->arbiterIndexes = NIL;
+ node->onConflictLockStrength = LCS_NONE;
node->onConflictSet = NIL;
node->onConflictCols = NIL;
node->onConflictWhere = NULL;
- node->onConflictLockingStrength = LCS_NONE;
- node->arbiterIndexes = NIL;
node->exclRelRTI = 0;
node->exclRelTlist = NIL;
}
@@ -7048,6 +7048,9 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
{
node->onConflictAction = onconflict->action;
+ /* Lock strength for ON CONFLICT SELECT [FOR UPDATE/SHARE] */
+ node->onConflictLockStrength = onconflict->lockStrength;
+
/*
* Here we convert the ON CONFLICT UPDATE tlist, if any, to the
* executor's convention of having consecutive resno's. The actual
@@ -7058,7 +7061,6 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
node->onConflictCols =
extract_update_targetlist_colnos(node->onConflictSet);
node->onConflictWhere = onconflict->onConflictWhere;
- node->onConflictLockingStrength = onconflict->lockingStrength;
/*
* If a set of unique index inference elements was provided (an
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 0a0335fedb7..120c98b4cfa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -923,16 +923,18 @@ infer_arbiter_indexes(PlannerInfo *root)
*/
if (indexOidFromConstraint == idxForm->indexrelid)
{
+ /*
+ * ON CONFLICT DO UPDATE/SELECT are not supported with exclusion
+ * constraints (they require a unique index, to ensure that there
+ * is only one conflicting row to update/select).
+ */
if (idxForm->indisexclusion &&
(onconflict->action == ONCONFLICT_UPDATE ||
onconflict->action == ONCONFLICT_SELECT))
- /* INSERT into an exclusion constraint can conflict with multiple rows.
- * So ON CONFLICT UPDATE OR SELECT would have to update/select mutliple rows
- * in those cases. Which seems weird - so block it with an error. */
ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("ON CONFLICT DO %s not supported with exclusion constraints",
- onconflict->action == ONCONFLICT_UPDATE ? "UPDATE" : "SELECT")));
+ errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("ON CONFLICT DO %s not supported with exclusion constraints",
+ onconflict->action == ONCONFLICT_UPDATE ? "UPDATE" : "SELECT"));
results = lappend_oid(results, idxForm->indexrelid);
list_free(indexList);
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a41516ee962..6ef3b9a4cf4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -668,10 +668,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->override = stmt->override;
+ /*
+ * ON CONFLICT DO UPDATE and ON CONFLICT DO SELECT FOR UPDATE/SHARE
+ * require UPDATE permission on the target relation.
+ */
requiresUpdatePerm = (stmt->onConflictClause &&
(stmt->onConflictClause->action == ONCONFLICT_UPDATE ||
(stmt->onConflictClause->action == ONCONFLICT_SELECT &&
- stmt->onConflictClause->lockingStrength != LCS_NONE)));
+ stmt->onConflictClause->lockStrength != LCS_NONE)));
/*
* We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
@@ -1273,8 +1277,8 @@ transformOnConflictClause(ParseState *pstate,
result->arbiterElems = arbiterElems;
result->arbiterWhere = arbiterWhere;
result->constraint = arbiterConstraint;
+ result->lockStrength = onConflictClause->lockStrength;
result->onConflictSet = onConflictSet;
- result->lockingStrength = onConflictClause->lockingStrength;
result->onConflictWhere = onConflictWhere;
result->exclRelIndex = exclRelIndex;
result->exclRelTlist = exclRelTlist;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 316587a8420..13e6c0de342 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12445,7 +12445,7 @@ opt_on_conflict:
$$->action = ONCONFLICT_SELECT;
$$->infer = $3;
$$->targetList = NIL;
- $$->lockingStrength = $6;
+ $$->lockStrength = $6;
$$->whereClause = $7;
$$->location = @1;
}
@@ -12456,7 +12456,7 @@ opt_on_conflict:
$$->action = ONCONFLICT_UPDATE;
$$->infer = $3;
$$->targetList = $7;
- $$->lockingStrength = LCS_NONE;
+ $$->lockStrength = LCS_NONE;
$$->whereClause = $8;
$$->location = @1;
}
@@ -12467,7 +12467,7 @@ opt_on_conflict:
$$->action = ONCONFLICT_NOTHING;
$$->infer = $3;
$$->targetList = NIL;
- $$->lockingStrength = LCS_NONE;
+ $$->lockStrength = LCS_NONE;
$$->whereClause = NULL;
$$->location = @1;
}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c5c4273208a..e8d1e01732e 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -3368,20 +3368,15 @@ transformOnConflictArbiter(ParseState *pstate,
*arbiterWhere = NULL;
*constraint = InvalidOid;
- if (onConflictClause->action == ONCONFLICT_UPDATE && !infer)
+ if ((onConflictClause->action == ONCONFLICT_UPDATE ||
+ onConflictClause->action == ONCONFLICT_SELECT) && !infer)
ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("ON CONFLICT DO UPDATE requires inference specification or constraint name"),
- errhint("For example, ON CONFLICT (column_name)."),
- parser_errposition(pstate,
- exprLocation((Node *) onConflictClause))));
- else if (onConflictClause->action == ONCONFLICT_SELECT && !infer)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("ON CONFLICT DO SELECT requires inference specification or constraint name"),
- errhint("For example, ON CONFLICT (column_name)."),
- parser_errposition(pstate,
- exprLocation((Node *) onConflictClause))));
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("ON CONFLICT DO %s requires inference specification or constraint name",
+ onConflictClause->action == ONCONFLICT_UPDATE ? "UPDATE" : "SELECT"),
+ errhint("For example, ON CONFLICT (column_name)."),
+ parser_errposition(pstate,
+ exprLocation((Node *) onConflictClause)));
/*
* To simplify certain aspects of its design, speculative insertion into
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index cf91c72d40b..aa377022df1 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -666,7 +666,7 @@ rewriteRuleAction(Query *parsetree,
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("ON CONFLICT DO SELECT requires a RETURNING clause"),
- errdetail("A rule action is INSERT ... ON CONFLICT DO SELECT, which requires a RETURNING clause"));
+ errdetail("A rule action is INSERT ... ON CONFLICT DO SELECT, which requires a RETURNING clause."));
/*
* If rule_action has a RETURNING clause, then either throw it away if the
@@ -3653,7 +3653,7 @@ rewriteTargetView(Query *parsetree, Relation view)
}
/*
- * For INSERT .. ON CONFLICT .. DO UPDATE/SELECT, we must also update
+ * For INSERT .. ON CONFLICT .. DO UPDATE/SELECT, we must also update
* assorted stuff in the onConflict data structure.
*/
if (parsetree->onConflict &&
@@ -3670,23 +3670,20 @@ rewriteTargetView(Query *parsetree, Relation view)
* For ON CONFLICT DO UPDATE, update the resnos in the auxiliary
* UPDATE targetlist to refer to columns of the base relation.
*/
- if (parsetree->onConflict->action == ONCONFLICT_UPDATE)
+ foreach(lc, parsetree->onConflict->onConflictSet)
{
- foreach(lc, parsetree->onConflict->onConflictSet)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
- TargetEntry *view_tle;
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ TargetEntry *view_tle;
- if (tle->resjunk)
- continue;
+ if (tle->resjunk)
+ continue;
- view_tle = get_tle_by_resno(view_targetlist, tle->resno);
- if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var))
- tle->resno = ((Var *) view_tle->expr)->varattno;
- else
- elog(ERROR, "attribute number %d not found in view targetlist",
- tle->resno);
- }
+ view_tle = get_tle_by_resno(view_targetlist, tle->resno);
+ if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var))
+ tle->resno = ((Var *) view_tle->expr)->varattno;
+ else
+ elog(ERROR, "attribute number %d not found in view targetlist",
+ tle->resno);
}
/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82e467a0b2f..5da4b0ff296 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7148,8 +7148,8 @@ get_insert_query_def(Query *query, deparse_context *context)
appendStringInfoString(buf, " DO SELECT");
/* Add FOR [KEY] UPDATE/SHARE clause if present */
- if (confl->lockingStrength != LCS_NONE)
- appendStringInfoString(buf, get_lock_clause_strength(confl->lockingStrength));
+ if (confl->lockStrength != LCS_NONE)
+ appendStringInfoString(buf, get_lock_clause_strength(confl->lockStrength));
/* Add a WHERE clause if given */
if (confl->onConflictWhere != NULL)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 297969efad3..cd5582f2485 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -433,8 +433,7 @@ typedef struct OnConflictActionState
TupleTableSlot *oc_Existing; /* slot to store existing target tuple in */
TupleTableSlot *oc_ProjSlot; /* CONFLICT ... SET ... projection target */
ProjectionInfo *oc_ProjInfo; /* for ON CONFLICT DO UPDATE SET */
- LockClauseStrength oc_LockingStrength; /* strength of lock for ON
- * CONFLICT DO SELECT, or LCS_NONE */
+ LockClauseStrength oc_LockStrength; /* lock strength for DO SELECT */
ExprState *oc_WhereClause; /* state for the WHERE clause */
} OnConflictActionState;
@@ -581,7 +580,7 @@ typedef struct ResultRelInfo
/* list of arbiter indexes to use to check conflicts */
List *ri_onConflictArbiterIndexes;
- /* ON CONFLICT evaluation state */
+ /* ON CONFLICT evaluation state for DO UPDATE/SELECT */
OnConflictActionState *ri_onConflict;
/* for MERGE, lists of MergeActionState (one per MergeMatchKind) */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 31c73abe87b..4db404f5429 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -200,7 +200,7 @@ typedef struct Query
/* OVERRIDING clause */
OverridingKind override pg_node_attr(query_jumble_ignore);
- OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
+ OnConflictExpr *onConflict; /* ON CONFLICT DO NOTHING/UPDATE/SELECT */
/*
* The following three fields describe the contents of the RETURNING list
@@ -1652,11 +1652,10 @@ typedef struct InferClause
typedef struct OnConflictClause
{
NodeTag type;
- OnConflictAction action; /* DO NOTHING, SELECT or UPDATE? */
+ OnConflictAction action; /* DO NOTHING, SELECT, or UPDATE */
InferClause *infer; /* Optional index inference clause */
- List *targetList; /* the target list (of ResTarget) */
- LockClauseStrength lockingStrength; /* strength of lock for DO SELECT, or
- * LCS_NONE */
+ LockClauseStrength lockStrength; /* lock strength for DO SELECT */
+ List *targetList; /* target list (of ResTarget) for DO UPDATE */
Node *whereClause; /* qualifications */
ParseLoc location; /* token location, or -1 if unknown */
} OnConflictClause;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index bdbbebd49fd..7b9debccb0f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -363,7 +363,7 @@ typedef struct ModifyTable
/* List of ON CONFLICT arbiter index OIDs */
List *arbiterIndexes;
/* lock strength for ON CONFLICT SELECT */
- LockClauseStrength onConflictLockingStrength;
+ LockClauseStrength onConflictLockStrength;
/* INSERT ON CONFLICT DO UPDATE targetlist */
List *onConflictSet;
/* target column numbers for onConflictSet */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index fe9677bdf3c..34302b42205 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2371,7 +2371,7 @@ typedef struct FromExpr
typedef struct OnConflictExpr
{
NodeTag type;
- OnConflictAction action; /* NONE, DO NOTHING, DO UPDATE, DO SELECT ? */
+ OnConflictAction action; /* DO NOTHING, SELECT, or UPDATE */
/* Arbiter */
List *arbiterElems; /* unique index arbiter list (of
@@ -2379,15 +2379,14 @@ typedef struct OnConflictExpr
Node *arbiterWhere; /* unique index arbiter WHERE clause */
Oid constraint; /* pg_constraint OID for arbiter */
- /* both ON CONFLICT SELECT and UPDATE */
- Node *onConflictWhere; /* qualifiers to restrict SELECT/UPDATE to */
+ /* ON CONFLICT DO SELECT */
+ LockClauseStrength lockStrength; /* strength of lock for DO SELECT */
- /* ON CONFLICT SELECT */
- LockClauseStrength lockingStrength; /* strength of lock for DO SELECT, or
- * LCS_NONE */
-
- /* ON CONFLICT UPDATE */
+ /* ON CONFLICT DO UPDATE */
List *onConflictSet; /* List of ON CONFLICT SET TargetEntrys */
+
+ /* both ON CONFLICT DO SELECT and UPDATE */
+ Node *onConflictWhere; /* qualifiers to restrict SELECT/UPDATE */
int exclRelIndex; /* RT index of 'excluded' relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
} OnConflictExpr;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index d760a7c8797..76e2355af20 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3586,7 +3586,7 @@ SELECT definition FROM pg_rules WHERE tablename = 'hats' ORDER BY rulename;
-- fails without RETURNING
INSERT INTO hats VALUES ('h7', 'blue');
ERROR: ON CONFLICT DO SELECT requires a RETURNING clause
-DETAIL: A rule action is INSERT ... ON CONFLICT DO SELECT, which requires a RETURNING clause
+DETAIL: A rule action is INSERT ... ON CONFLICT DO SELECT, which requires a RETURNING clause.
-- works (returns conflicts)
EXPLAIN (costs off)
INSERT INTO hats VALUES ('h7', 'blue') RETURNING *;
--
2.48.1