diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c98c9b5..8a22b8c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3755,8 +3755,10 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 	if (node->onConflictAction != ONCONFLICT_NONE)
 	{
 		ExplainPropertyText("Conflict Resolution",
-							node->onConflictAction == ONCONFLICT_NOTHING ?
-							"NOTHING" : "UPDATE",
+							node->onConflictAction == ONCONFLICT_NOTHING
+							? "NOTHING"
+							: node->onConflictAction == ONCONFLICT_SELECT
+							? "SELECT" : "UPDATE",
 							es);
 
 		/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..8e64061 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -545,6 +545,8 @@ ExecInsert(ModifyTableState *mtstate,
 					else
 						goto vlock;
 				}
+				/* committed conflict tuple found */
+
 				else
 				{
 					/*
@@ -558,11 +560,26 @@ ExecInsert(ModifyTableState *mtstate,
 					 * type. As there's no conflicting usage of
 					 * ExecGetReturningSlot() in the DO NOTHING case...
 					 */
-					Assert(onconflict == ONCONFLICT_NOTHING);
+					Assert(onconflict == ONCONFLICT_NOTHING || onconflict == ONCONFLICT_SELECT);
 					ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid,
 										ExecGetReturningSlot(estate, resultRelInfo));
 					InstrCountTuples2(&mtstate->ps, 1);
-					return NULL;
+					if (onconflict == ONCONFLICT_SELECT && resultRelInfo->ri_projectReturning)
+					{
+						TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
+						TM_FailureData tmfd;
+						TM_Result	   test = table_tuple_lock(resultRelInfo->ri_RelationDesc, &conflictTid,
+															   estate->es_snapshot,
+															   existing, estate->es_output_cid,
+															   LockTupleShare, LockWaitBlock, 0,
+															   &tmfd);
+						if (test == TM_Ok)
+						{
+							result = ExecProcessReturning(resultRelInfo, existing, planSlot);
+						}
+						ExecClearTuple(existing);
+					}
+					return result;
 				}
 			}
 
@@ -2542,7 +2559,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * If needed, Initialize target list, projection and qual for ON CONFLICT
 	 * DO UPDATE.
 	 */
-	if (node->onConflictAction == ONCONFLICT_UPDATE)
+	if (node->onConflictAction == ONCONFLICT_UPDATE || node->onConflictAction == ONCONFLICT_SELECT)
 	{
 		ExprContext *econtext;
 		TupleDesc	relationDesc;
@@ -2566,35 +2583,37 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			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.
-		 */
-		tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
-		resultRelInfo->ri_onConflict->oc_ProjSlot =
-			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
-								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
-		/* build UPDATE SET projection state */
-		resultRelInfo->ri_onConflict->oc_ProjInfo =
-			ExecBuildProjectionInfo(node->onConflictSet, econtext,
-									resultRelInfo->ri_onConflict->oc_ProjSlot,
-									&mtstate->ps,
-									relationDesc);
-
-		/* initialize state to evaluate the WHERE clause, if any */
-		if (node->onConflictWhere)
+		if (node->onConflictAction == ONCONFLICT_UPDATE)
 		{
-			ExprState  *qualexpr;
+			/*
+			 * 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.
+			 */
+			tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
+			resultRelInfo->ri_onConflict->oc_ProjSlot =
+				ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+									   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+			/* build UPDATE SET projection state */
+			resultRelInfo->ri_onConflict->oc_ProjInfo =
+				ExecBuildProjectionInfo(node->onConflictSet, econtext,
+										resultRelInfo->ri_onConflict->oc_ProjSlot,
+										&mtstate->ps,
+										relationDesc);
+
+			/* initialize state to evaluate the WHERE clause, if any */
+			if (node->onConflictWhere)
+			{
+				ExprState  *qualexpr;
 
-			qualexpr = ExecInitQual((List *) node->onConflictWhere,
+				qualexpr = ExecInitQual((List *) node->onConflictWhere,
 									&mtstate->ps);
-			resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+				resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+			}
 		}
 	}
-
 	/*
 	 * If we have any secondary relations in an UPDATE or DELETE, they need to
 	 * be treated like non-locked relations in SELECT FOR UPDATE, ie, the
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4..1e815d1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10874,6 +10874,16 @@ opt_on_conflict:
 					$$->whereClause = NULL;
 					$$->location = @1;
 				}
+			|
+			ON CONFLICT opt_conf_expr DO SELECT
+				{
+					$$ = makeNode(OnConflictClause);
+					$$->action = ONCONFLICT_SELECT;
+					$$->infer = $3;
+					$$->targetList = NIL;
+					$$->whereClause = NULL;
+					$$->location = @1;
+				}
 			| /*EMPTY*/
 				{
 					$$ = NULL;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fe777c3..e96f568 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3612,6 +3612,11 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 										rt_entry_relation,
 										parsetree->resultRelation);
 			}
+			if (parsetree->onConflict &&
+				parsetree->onConflict->action == ONCONFLICT_SELECT)
+			{
+				parsetree->onConflict->onConflictSet =parsetree->targetList;
+			}
 		}
 		else if (event == CMD_UPDATE)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60dd80c..cd40985 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6276,6 +6276,10 @@ get_insert_query_def(Query *query, deparse_context *context)
 		{
 			appendStringInfoString(buf, " DO NOTHING");
 		}
+		else if (confl->action == ONCONFLICT_SELECT)
+		{
+			appendStringInfoString(buf, " DO SELECT");
+		}
 		else
 		{
 			appendStringInfoString(buf, " DO UPDATE SET ");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b..c23d3d2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -823,7 +823,8 @@ typedef enum OnConflictAction
 {
 	ONCONFLICT_NONE,			/* No "ON CONFLICT" clause */
 	ONCONFLICT_NOTHING,			/* ON CONFLICT ... DO NOTHING */
-	ONCONFLICT_UPDATE			/* ON CONFLICT ... DO UPDATE */
+	ONCONFLICT_UPDATE,			/* ON CONFLICT ... DO UPDATE */
+	ONCONFLICT_SELECT			/* ON CONFLICT ... DO SELECT */
 } OnConflictAction;
 
 /*
