merge command - GSoC progress

Started by Boxuan Zhaiover 15 years ago22 messages
#1Boxuan Zhai
bxzhai2010@gmail.com
1 attachment(s)

---------- Forwarded message ----------
From: Boxuan Zhai <bxzhai2010@gmail.com>
Date: 2010/7/26
Subject: Re: GSoC progress
To: Robert Haas <robertmhaas@gmail.com>
Cc: Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>

Hi,

I have get a edition that the merge command can run. It accept the standard
merge command and can do UPDATE, INSERT and DELETE actions now. But we
cannot put additional qualification for actions. There are some bugs when we
try to evaluate the quals which make the system quit. I will fix it soon.

The merge actions are packed in ModifyTable nodes now. This can benefit the
coding if we want to implement MERGE ... RETURNING ...

Please find the patch file in attachment.

2010/7/24 Robert Haas <robertmhaas@gmail.com>

Show quoted text

On Fri, Jul 23, 2010 at 12:21 PM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

I my current implementation, the overall merge command plan is still a
single ModifyTable node. And the main query of merge command is in the

field

of ModifyTable.plans. I add a new "List * mergeActPstates" field in the
structure of ModifyTableState. When apply the ExecInitModifyTable()

function

on a ModifyTable node of a merge command, the function will do an extra

work

to init all the merge actions and put the result into the
list of mergeActPstates. (in previous design, I put the list in
PlannedStmt, but that makes the code a little bit ugly).

Hmm, that might work.

I now put all merge-command-related functions in the file of
nodeModifyTable.c. and I have fixed something that I missed in the init
process for merge command, such as set the junkfiler attribute for ctid.
However, I still cannot run ExecProject over the tuple slot returned

by main

query.

In fact, I am not sure how ExecProject () works exactly. It totally

depends

on the correctness of the input ProjectionInfo, in which we have a
"ExprContext *pi_exprContext" parameter. The expression context should
contains all the tuple slot needed for the projection. I
init ProjectionInfo correctly, but the expression context is empty when
initialized. Could you tell me WHEN does the system fill the tuple slots
(the ecxt_scantuple, ecxt_innertuple, ecxt_outertuple) in the expression
context of the ProjectionInfo structure?

I'm not real familiar with this part of the code; that would be a good
question to ask on pgsql-hackers. But I believe the answer is that
the individual node types initialize the expression context at the
appropriate time. For example, if you grep for ecxt_innertuple,
you'll find that it's set in the hash, merge, and nestloop nodes;
whereas the scan tuple is set by the scan, bitmap heap, and index scan
nodes.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

Attachments:

merge_run_no_qual.patchtext/plain; charset=US-ASCII; name=merge_run_no_qual.patchDownload
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 6ec73f0..16b0613 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1142,7 +1142,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	if (attnum <= 0)
 	{
 		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
+			elog(ERROR, "cannot extract system attribute from virtual tuple for attr %d", attnum);
 		if (tuple == &(slot->tts_minhdr))		/* internal error */
 			elog(ERROR, "cannot extract system attribute from minimal tuple");
 		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2f33fdb..0326555 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,6 +171,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_INSERT:
 		case CMD_DELETE:
 		case CMD_UPDATE:
+		case CMD_MERGE:	
 			estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
@@ -795,6 +796,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		i++;
 	}
 
+	
+
 	/*
 	 * Initialize the private state information for all the nodes in the query
 	 * tree.  This opens files, allocates storage and leaves us ready to start
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_MergeAction:
+			result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+													   estate, eflags);
+			break;
+			
 		case T_Append:
 			result = (PlanState *) ExecInitAppend((Append *) node,
 												  estate, eflags);
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 9808274..6cdab90 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -5237,7 +5237,6 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 	/* Assume single result row until proven otherwise */
 	if (isDone)
 		*isDone = ExprSingleResult;
-
 	/*
 	 * Clear any former contents of the result slot.  This makes it safe for
 	 * us to use the slot's Datum/isnull arrays as workspace. (Also, we can
@@ -5245,6 +5244,7 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 	 */
 	ExecClearTuple(slot);
 
+
 	/*
 	 * Force extraction of all input values that we'll need.  The
 	 * Var-extraction loops below depend on this, and we are also prefetching
@@ -5260,6 +5260,7 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 		slot_getsomeattrs(econtext->ecxt_scantuple,
 						  projInfo->pi_lastScanVar);
 
+
 	/*
 	 * Assign simple Vars to result by direct extraction of fields from source
 	 * slots ... a mite ugly, but fast ...
@@ -5304,6 +5305,7 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 		}
 	}
 
+
 	/*
 	 * If there are any generic expressions, evaluate them.  It's possible
 	 * that there are set-returning functions in such expressions; if so and
@@ -5321,6 +5323,7 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 			return slot;		/* no more result rows, return empty slot */
 	}
 
+
 	/*
 	 * Successfully formed a result row.  Mark the result slot as containing a
 	 * valid virtual tuple.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 877abf4..0322616 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -445,7 +445,6 @@ ExecAssignResultTypeFromTL(PlanState *planstate)
 {
 	bool		hasoid;
 	TupleDesc	tupDesc;
-
 	if (ExecContextForcesOids(planstate, &hasoid))
 	{
 		/* context forces OID choice; hasoid is now set correctly */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..fe477b6 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -583,6 +583,103 @@ lreplace:;
 }
 
 
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   ModifyTableState *node,
+		   EState *estate)
+{
+
+	TupleTableSlot *actslot = NULL;
+	TupleTableSlot *res = NULL;
+	ListCell *each;
+		
+	foreach(each, node->mergeActPstates)
+	{
+		ModifyTableState *actpstate;
+
+		PlanState *pstate; 
+
+		actpstate= (ModifyTableState *)lfirst(each);
+		
+		Assert(actpstate->mt_nplans == 1);
+
+		pstate = actpstate->mt_plans[0];
+
+		
+
+
+		/*
+		*	If tupleid == NULL, then it is a NOT MATCHED case, in which we can only do INSERT action
+		*	If tupleid != NULL, then it is a MATCHED case, in which we can only do UPDATE or DELETE action
+		*/
+		
+		if((tupleid == NULL && actpstate->operation != CMD_INSERT) ||
+			(tupleid != NULL && actpstate->operation == CMD_INSERT))
+		{
+			continue;
+		}
+
+
+		ResetExprContext(pstate->ps_ExprContext);
+
+		/*
+		If the action has an additional qual, which is not satisfied, skip it
+		*/	
+		
+		if(pstate->qual && !ExecQual(pstate->qual, pstate->ps_ExprContext,false))
+		{	
+zbxprint("the qual is not satisfied ");
+		
+			continue;
+		}
+
+		/*
+		*	Now we start to exec this action. Firstly , project the result tuple slot. 
+		*	The merge action result slot is projected in the same way as a returning slot. 
+		*	so we call ExecProcessReturning() directly here.
+		*/
+		actslot = ExecProcessReturning(pstate->ps_ProjInfo, slot, planSlot);
+	
+			
+		switch (actpstate->operation)
+		{
+			case CMD_INSERT:
+				res = ExecInsert(actslot, planSlot, estate);
+				break;
+			case CMD_UPDATE:
+				res = ExecUpdate(tupleid,
+							actslot,
+							planSlot,
+							&actpstate->mt_epqstate,
+							estate);
+				break;
+			case CMD_DELETE:
+				res = ExecDelete(tupleid, 
+							planSlot, 
+							&actpstate->mt_epqstate,
+							estate);
+				break;
+			default:
+				elog(ERROR, "unknown merge action type");
+				break;
+		}
+
+
+		/*
+		* The choosen action is executed, no need to do the remain actions
+		*/	
+		return res;
+	}
+
+	/*
+	* Here, no action is taken. Let's pass this slot
+	*/	
+	return NULL;
+	
+}
+
 /*
  * Process BEFORE EACH STATEMENT triggers
  */
@@ -603,6 +700,9 @@ fireBSTriggers(ModifyTableState *node)
 			ExecBSDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the BS triggers of merge\n");
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -629,6 +729,9 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the AS triggers of merge\n");
+			break;	
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -654,7 +757,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
-
+zbxprint("enter the ExecModifyTable\n");
 	/*
 	 * On first call, fire BEFORE STATEMENT triggers before proceeding.
 	 */
@@ -683,6 +786,7 @@ ExecModifyTable(ModifyTableState *node)
 	for (;;)
 	{
 		planSlot = ExecProcNode(subplanstate);
+	zbxprint("ExecModifyTable get one tuple\n");
 
 		if (TupIsNull(planSlot))
 		{
@@ -699,30 +803,53 @@ ExecModifyTable(ModifyTableState *node)
 			else
 				break;
 		}
+zbxprint("ExecModifyTable get one tuple 1\n");
 
 		EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
 		slot = planSlot;
-
+print_slot(slot);
 		if (junkfilter != NULL)
 		{
 			/*
 			 * extract the 'ctid' junk attribute.
 			 */
-			if (operation == CMD_UPDATE || operation == CMD_DELETE)
+			if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 			{
 				Datum		datum;
 				bool		isNull;
 
+
+
+			zbxprint("ExecModifyTable the junkattrno is %d\n", junkfilter->jf_junkAttNo);
+
+
 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
 											 &isNull);
-				/* shouldn't ever get a null result... */
+				zbxprint("ExecModifyTable get one tuple 2\n");
+				
 				if (isNull)
-					elog(ERROR, "ctid is NULL");
-
-				tupleid = (ItemPointer) DatumGetPointer(datum);
-				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
-				tupleid = &tuple_ctid;
+				{
+					/*
+					*	shouldn't ever get a null result for update and delete. 
+					*	Merge command will get a null ctid if in "NOT MATCHED" case
+					*/
+					if(operation != CMD_MERGE)
+						elog(ERROR, "ctid is NULL");
+				}	
+				else
+				{
+					zbxprint("ExecModifyTable get ctid the\n");
+				
+					tupleid = (ItemPointer) DatumGetPointer(datum);
+					
+					zbxprint("ExecModifyTable get ctid is %d\n", tupleid);
+					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
+					tupleid = &tuple_ctid;
+					
+					zbxprint("ExecModifyTable get one tuple 2.4\n");
+				}
 			}
+		zbxprint("ExecModifyTable get one tuple 2.5\n");
 
 			/*
 			 * apply the junkfilter if needed.
@@ -730,6 +857,7 @@ ExecModifyTable(ModifyTableState *node)
 			if (operation != CMD_DELETE)
 				slot = ExecFilterJunk(junkfilter, slot);
 		}
+		zbxprint("ExecModifyTable get one tuple 3\n");
 
 		switch (operation)
 		{
@@ -744,6 +872,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecDelete(tupleid, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
+			case CMD_MERGE:
+				slot = ExecMerge(tupleid, slot, planSlot,
+								  node, estate);
+				break;	
+				
 			default:
 				elog(ERROR, "unknown operation");
 				break;
@@ -771,6 +904,102 @@ ExecModifyTable(ModifyTableState *node)
 	return NULL;
 }
 
+
+
+
+
+
+
+
+/*
+*	When init a merge plan, we also need init its action plans. 
+*	However, these action plans are "pure" plans. We only want to handle the tlist and qual
+*	Thus, the return result is a plain "PlanState". We dont have a MeregeActionState kind of thing.
+*/
+PlanState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+	PlanState *result;
+	
+	/*
+	 * do nothing when we get to the end of a leaf on tree.
+	 */
+	if (node == NULL)
+		return NULL;
+
+		
+	/*
+	 * create state structure
+	 */
+	result = makeNode(PlanState);
+	result->plan = (Plan *)node;
+	result->state = estate;
+	zbxprint("ExecInitMergeAction 3\n");
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, result);
+
+
+	
+	zbxprint("ExecInitMergeAction 1\n");
+	/*
+	 * initialize tuple type and projection info
+	 */
+	ExecAssignResultTypeFromTL(result);
+	zbxprint("ExecInitMergeAction 2\n");
+
+
+
+
+		
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	 
+	ExecAssignExprContext(estate, result);
+	zbxprint("ExecInitMergeAction 4\n");
+
+
+	/*
+	 * initialize child expressions
+	 */
+	result->targetlist = (List *)
+		ExecInitExpr((Expr *) node->plan.targetlist,  result);
+
+	
+	zbxprint("ExecInitMergeAction 5\n");
+	result->qual = (List *)
+		ExecInitExpr((Expr *) node->plan.qual, result);
+
+	
+	zbxprint("ExecInitMergeAction 6\n");
+	/*
+	* init the projection information
+	*/
+	ExecAssignProjectionInfo(result, NULL);
+
+/*
+printf("before check plan output for merge actio \n");	
+if(estate->es_result_relation_info == NULL)
+	printf("re infro is null\n");
+
+if(estate->es_result_relation_info->ri_RelationDesc == NULL)
+	printf("r relation desc is nUll\n");
+	ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+							node->targetlist);
+printf("AFTER check plan output for merge actio \n");	
+*/
+	return result;
+}
+
+
+
+
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -786,7 +1015,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	bool 		isMergeAction = false;
 
+	
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -797,7 +1028,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 */
 	if (estate->es_epqTuple != NULL)
 		elog(ERROR, "ModifyTable should not be called during EvalPlanQual");
-
 	/*
 	 * create state structure
 	 */
@@ -826,12 +1056,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+		if(IsA(subplan, MergeAction))
+			isMergeAction = true;
+		zbxprint("ExecInitModifyTable 2.4\n");
+		
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+		zbxprint("ExecInitModifyTable2.5\n");
+
 		estate->es_result_relation_info++;
 		i++;
 	}
+	
 	estate->es_result_relation_info = NULL;
 
+	
 	/* select first subplan */
 	mtstate->mt_whichplan = 0;
 	subplan = (Plan *) linitial(node->plans);
@@ -955,7 +1193,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
-				junk_filter_needed = true;
+			case CMD_MERGE:	
+				if(!isMergeAction)
+					junk_filter_needed = true;
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -973,15 +1213,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
 					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
 										subplan->targetlist);
-
+				
 				j = ExecInitJunkFilter(subplan->targetlist,
 							resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
 									   ExecInitExtraTupleSlot(estate));
 
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
+				if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 				{
 					/* For UPDATE/DELETE, find the ctid junk attr now */
 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+
 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
 						elog(ERROR, "could not find junk ctid column");
 				}
@@ -1006,6 +1247,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (estate->es_trig_tuple_slot == NULL)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	
+	/*
+	* for the merge actions, we need to similar things as above
+	*/
+		zbxprint("ExecInitModifyTable last step , init the action nodes\n");
+
+	foreach(l, node->mergeActPlan)
+	{
+		PlanState *actpstate = ExecInitNode((Plan *)lfirst(l),  estate, 0);
+		/*
+		* put the pstates of each action into ModifyTableState
+		*/
+		mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+		
+	}
+	
 	return mtstate;
 }
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 93dcef5..f0be5e8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(mergeActPlan);
 
 	return newnode;
 }
@@ -2273,6 +2274,10 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 
+	COPY_SCALAR_FIELD(isMergeAction);
+	/*merge action list*/
+	COPY_NODE_FIELD(mergeActQry); 
+
 	return newnode;
 }
 
@@ -2343,6 +2348,64 @@ _copySelectStmt(SelectStmt *from)
 	return newnode;
 }
 
+
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+	MergeStmt *newnode = makeNode(MergeStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(source);
+	COPY_NODE_FIELD(matchCondition);
+	COPY_NODE_FIELD(actions);
+	
+	return newnode;
+	
+}
+
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+	MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+	COPY_SCALAR_FIELD(match);
+	COPY_NODE_FIELD(condition);
+	COPY_NODE_FIELD(action);
+
+	return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+	MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+	newNode->type = T_MergeUpdate;
+
+	return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+	MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+	newNode->type = T_MergeInsert;
+
+	return newNode;
+}
+
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+	MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+	newNode->type = T_MergeDelete;
+
+	return newNode;
+}
+
+
+
 static SetOperationStmt *
 _copySetOperationStmt(SetOperationStmt *from)
 {
@@ -3902,7 +3965,22 @@ copyObject(void *from)
 			break;
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
-			break;
+			break;		
+		case T_MergeStmt:
+			retval = _copyMergeStmt(from);
+			break;			
+		case T_MergeConditionAction:
+			retval = _copyMergeConditionAction(from);
+			break;	
+		case T_MergeUpdate:
+			retval = _copyMergeUpdate(from);
+			break;
+		case T_MergeInsert:
+			retval = _copyMergeInsert(from);
+			break;	
+		case T_MergeDelete:
+			retval = _copyMergeDelete(from);
+			break;	
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5d83727..572f6df 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,6 +878,7 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
 
+	COMPARE_SCALAR_FIELD(isMergeAction);
 	return true;
 }
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 79baf4f..4b3dd60 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(mergeActPlan);
 }
 
 static void
@@ -2019,9 +2020,52 @@ _outQuery(StringInfo str, Query *node)
 	WRITE_NODE_FIELD(limitCount);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(setOperations);
+	WRITE_BOOL_FIELD(isMergeAction);
+	WRITE_NODE_FIELD(mergeActQry);
+}
+
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+	WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+	WRITE_BOOL_FIELD(match);
+	
+	WRITE_NODE_FIELD(condition);
+	WRITE_NODE_FIELD(action);
+
+
 }
 
 static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+	WRITE_NODE_TYPE("MERGESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(source);
+	WRITE_NODE_FIELD(matchCondition);
+	WRITE_NODE_FIELD(actions);
+
+}
+
+static void 
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+	WRITE_NODE_TYPE("DELETESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(usingClause);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_NODE_FIELD(returningList);
+
+
+}
+
+
+
+static void
 _outSortGroupClause(StringInfo str, SortGroupClause *node)
 {
 	WRITE_NODE_TYPE("SORTGROUPCLAUSE");
@@ -2905,6 +2949,17 @@ _outNode(StringInfo str, void *obj)
 				_outXmlSerialize(str, obj);
 				break;
 
+			case T_MergeStmt:
+				_outMergeStmt(str, obj);
+				break;
+			case T_MergeConditionAction:
+				_outMergeConditionAction(str,obj);
+				break;
+			case T_DeleteStmt:
+				_outDeleteStmt(str,obj);
+				break;
+					
+
 			default:
 
 				/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2115cc0..9f6e6fe 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3973,6 +3973,7 @@ make_modifytable(CmdType operation, List *resultRelations,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..f07afba 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -194,7 +194,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 		/* Default assumption is we need all the tuples */
 		tuple_fraction = 0.0;
 	}
-
+printf("going to do the top planner\n");
 	/* primary planning entry point (may recurse for subqueries) */
 	top_plan = subquery_planner(glob, parse, NULL,
 								false, tuple_fraction, &root);
@@ -220,6 +220,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	Assert(list_length(glob->subplans) == list_length(glob->subrowmarks));
 	lrt = list_head(glob->subrtables);
 	lrm = list_head(glob->subrowmarks);
+
+printf("set supble refers\n");	
 	foreach(lp, glob->subplans)
 	{
 		Plan	   *subplan = (Plan *) lfirst(lp);
@@ -565,6 +567,28 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											 returningLists,
 											 rowMarks,
 											 SS_assign_special_param(root));
+
+
+
+			/*do a simple plan for each actions in the merge command.
+			*put them in mergeActPlan list;
+			*/
+			if(parse->commandType == CMD_MERGE)
+			{
+				ListCell *l;
+				ModifyTable *top_plan = (ModifyTable *)plan;
+				foreach(l, parse->mergeActQry)
+				{
+					
+					Plan *actplan = (Plan *)merge_action_planner(glob, 
+																		(Query *)lfirst(l),
+																		(Plan *)linitial(top_plan->plans)
+																	);
+
+					top_plan->mergeActPlan = lappend(top_plan->mergeActPlan, actplan);
+				}
+
+			}
 		}
 	}
 
@@ -584,6 +608,126 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	return plan;
 }
 
+
+
+ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+				 Plan *top_plan)
+{
+	PlannerInfo *root;
+	MergeAction	*actplan;
+	ModifyTable *result;
+	
+	List	   	*returningLists;
+	List 		*rowMarks;
+
+	/* Create a PlannerInfo data structure for this subquery */
+	root = makeNode(PlannerInfo);
+	root->parse = parse;
+	root->glob = glob;
+	root->query_level = 1;
+	root->parent_root = NULL;
+	root->planner_cxt = CurrentMemoryContext;
+	root->init_plans = NIL;
+	root->cte_plan_ids = NIL;
+	root->eq_classes = NIL;
+	root->append_rel_list = NIL;
+
+	root->hasRecursion = false;
+	root->wt_param_id = -1;
+	root->non_recursive_plan = NULL;
+
+
+zbxprint("the query node for palnner is \n%s\n", nodeToString(parse));
+
+	/*
+	 * no having clause in a merge action
+	 */
+	Assert(parse->havingQual == NULL);
+
+	/* Clear this flag; might get set in distribute_qual_to_rels */
+	root->hasPseudoConstantQuals = false;
+
+	/*
+	 * Do expression preprocessing on targetlist and quals.
+	 */
+
+zbxprint("before targelist \n"); 
+	
+	parse->targetList = (List *)
+		preprocess_expression(root, (Node *) parse->targetList,
+							  EXPRKIND_TARGET);
+
+zbxprint("before qual condition \n");	
+	preprocess_qual_conditions(root, (Node *) parse->jointree);
+
+	
+zbxprint("after qual condition \n"); 
+
+
+	/*
+	 * Do the main planning.  If we have an inherited target relation, that
+	 * needs special processing, else go straight to grouping_planner.
+	 */
+
+
+	actplan = makeNode(MergeAction);
+	actplan->operation = parse->commandType;
+
+	/*copy the cost from the top_plan*/
+	actplan->plan.startup_cost = top_plan->startup_cost;
+	actplan->plan.total_cost = top_plan->total_cost;
+	actplan->plan.plan_rows = top_plan->plan_rows;
+	actplan->plan.plan_width = top_plan->plan_width;
+	
+	
+zbxprint("before tlist preprocess \n"); 
+
+	actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+if(parse->jointree == NULL)
+	zbxprint("join tree is null \n"); 
+
+	if(parse->jointree->quals != NULL && IsA(parse->jointree->quals , List))
+		actplan->plan.qual = (List *)parse->jointree->quals;
+	else
+		zbxprint("the qual in from expr is not a list\n");
+	
+	if (parse->returningList)
+	{
+		List	   *rlist;
+
+		Assert(parse->resultRelation);
+		rlist = set_returning_clause_references(root->glob,
+												parse->returningList,
+												&actplan->plan,
+											  parse->resultRelation);
+		returningLists = list_make1(rlist);
+	}
+	else
+		returningLists = NIL;
+
+
+	if (parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
+zbxprint("make modifty table \n"); 
+
+	
+	result = make_modifytable(parse->commandType,
+										   copyObject(root->resultRelations),
+											 list_make1(actplan),
+											 returningLists,
+											 rowMarks,
+											 SS_assign_special_param(root));
+
+	zbxprint("after make modifty table \n"); 
+	
+	return result;
+}
+
+
 /*
  * preprocess_expression
  *		Do subquery_planner's preprocessing work for an expression,
@@ -608,6 +752,8 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	 * can skip it in VALUES lists, however, since they can't contain any Vars
 	 * at all.
 	 */
+
+printf("before flattern join alias vars\n");	
 	if (root->hasJoinRTEs && kind != EXPRKIND_VALUES)
 		expr = flatten_join_alias_vars(root, expr);
 
@@ -626,6 +772,9 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	 * careful to maintain AND/OR flatness --- that is, do not generate a tree
 	 * with AND directly under AND, nor OR directly under OR.
 	 */
+
+	printf("before evalu const vars\n");	
+	
 	expr = eval_const_expressions(root, expr);
 
 	/*
@@ -982,7 +1131,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		if (parse->groupClause)
 			preprocess_groupclause(root);
 		numGroupCols = list_length(parse->groupClause);
-
 		/* Preprocess targetlist */
 		tlist = preprocess_targetlist(root, tlist);
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..6eb3d5a 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -55,7 +55,6 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	ListCell   *lc;
-
 	/*
 	 * Sanity check: if there is a result relation, it'd better be a real
 	 * relation not a subquery.  Else parser or rewriter messed up.
@@ -78,17 +77,25 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 								  result_relation, range_table);
 
 	/*
-	 * for "update" and "delete" queries, add ctid of the result relation into
+	 * for "update" , "delete"  and "merge" queries, add ctid of the result relation into
 	 * the target list so that the ctid will propagate through execution and
 	 * ExecutePlan() will be able to identify the right tuple to replace or
 	 * delete.	This extra field is marked "junk" so that it is not stored
 	 * back into the tuple.
+	 *  
+	 * BUT, if the query node is a merge action, we don't need to expend the ctid attribute in tlist.
+	 * The tlist of the merge top level plan already contains a "ctid" junk attr of the target relation.
 	 */
-	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+	 
+	if(!parse->isMergeAction  &&  
+			(command_type == CMD_UPDATE || 
+			command_type == CMD_DELETE || 
+			command_type == CMD_MERGE))
 	{
 		TargetEntry *tle;
 		Var		   *var;
 
+
 		var = makeVar(result_relation, SelfItemPointerAttributeNumber,
 					  TIDOID, -1, 0);
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..66287ab 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,8 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
 static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
 static List *transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos);
+
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +166,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			 * Optimizable statements
 			 */
 		case T_InsertStmt:
+		case T_MergeInsert:
 			result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
 			break;
 
 		case T_DeleteStmt:
+		case T_MergeDelete:
 			result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
 			break;
 
 		case T_UpdateStmt:
+		case T_MergeUpdate:
 			result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
 			break;
 
+		case T_MergeStmt:
+			result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+			break; 
+
 		case T_SelectStmt:
 			{
 				SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +291,33 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	qry->commandType = CMD_DELETE;
 
-	/* set up range table with just the result rel */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_DELETE);
-
 	qry->distinctClause = NIL;
 
+	if(IsA(stmt,MergeDelete))
+		qry->isMergeAction = true;
 	/*
-	 * The USING clause is non-standard SQL syntax, and is equivalent in
-	 * functionality to the FROM list that can be specified for UPDATE. The
-	 * USING keyword is used rather than FROM because FROM is already a
-	 * keyword in the DELETE syntax.
-	 */
-	transformFromClause(pstate, stmt->usingClause);
+	* The input stmt could be a MergeDelete node. 
+	* In this case, we don't need the process on range table.
+	*/
+	if(IsA(stmt, DeleteStmt))
+	{
+		/* set up range table with just the result rel */
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_DELETE);
+
+		
 
+		/*
+		 * The USING clause is non-standard SQL syntax, and is equivalent in
+		 * functionality to the FROM list that can be specified for UPDATE. The
+		 * USING keyword is used rather than FROM because FROM is already a
+		 * keyword in the DELETE syntax.
+		 */
+		transformFromClause(pstate, stmt->usingClause);
+	}
+	
 	qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -342,11 +362,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->commandType = CMD_INSERT;
 	pstate->p_is_insert = true;
 
+	if(IsA(stmt,MergeInsert))
+		qry->isMergeAction = true;
+	
 	/*
 	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
 	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
 	 * efficiency and so we can handle DEFAULT specifications.
 	 */
+
+	/*a MergeInsert statment is always a VALUE clause*/
 	isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
 	/*
@@ -382,7 +407,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * mentioned in the SELECT part.  Note that the target table is not added
 	 * to the joinlist or namespace.
 	 */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
+	if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/ 
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
 										 false, false, ACL_INSERT);
 
 	/* Validate stmt->cols list, or build default list if no list given */
@@ -799,6 +825,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -1730,16 +1757,27 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_update = true;
 
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_UPDATE);
+	if(IsA(stmt,MergeUpdate))
+		qry->isMergeAction = true;
+
+	
+	if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+	{
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_UPDATE);
+
+		/*
+		 * the FROM clause is non-standard SQL syntax. We used to be able to do
+		 * this with REPLACE in POSTQUEL so we keep the feature.
+		 */
+		transformFromClause(pstate, stmt->fromClause);
+	}
+
+zbxprint("the rel name space is \n%s\n", nodeToString(pstate->p_relnamespace));
+zbxprint("the var name space is \n%s\n", nodeToString(pstate->p_varnamespace));
 
-	/*
-	 * the FROM clause is non-standard SQL syntax. We used to be able to do
-	 * this with REPLACE in POSTQUEL so we keep the feature.
-	 */
-	transformFromClause(pstate, stmt->fromClause);
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -2241,3 +2279,336 @@ applyLockingClause(Query *qry, Index rtindex,
 	rc->pushedDown = pushedDown;
 	qry->rowMarks = lappend(qry->rowMarks, rc);
 }
+
+
+
+/*transform a action of merge command into a query. 
+No change of the pstate range table is allowed in this function. 
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+	Query *actqry = makeNode(Query);
+	A_Expr *match_expr; //the expr of matched/not matched
+	A_Expr *act_qual_expr;
+
+	/*firstly, we need to make sure that DELETE and UPDATE actions are only taken in MATCHED condition 
+	and INSERTs are only takend when not MATCHED
+	*/
+	if(IsA(condact->action, MergeDelete)) 
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{	
+		if(condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+	}
+	else
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("UNKONWN action type in MERGE")));
+
+
+		
+	/*combine the condtion of this act with the ON qual of the merge command
+	do a copy of the merge condtion for safety. 
+	*/
+
+	/*
+	if(condact->match) 
+		match_expr = copyObject(stmt->matchCondition);
+	else
+		match_expr = makeA_Expr(AEXPR_NOT, NIL, NULL, 
+									copyObject(stmt->matchCondition), 1);
+											
+
+	if(condact->condition)
+		act_qual_expr = makeA_Expr(AEXPR_AND, NIL, condact->condition, (Node *)match_expr, 2);
+	else
+		act_qual_expr = match_expr;
+
+	*/
+
+	act_qual_expr = condact->condition;
+	
+	/*use the transfomStmt() to parse all types of actions*/
+	if(IsA(condact->action, MergeDelete))
+	{
+		/*a delete action*/
+		MergeDelete *deleteact = (MergeDelete *)(condact->action);
+		Assert(IsA(deleteact,DeleteStmt));
+		
+		deleteact->relation = stmt->relation;
+		deleteact->usingClause = stmt->source;
+		deleteact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)deleteact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_DELETE || actqry->utilityStmt != NULL)
+			elog(ERROR, "improper DELETE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{
+		/*an update action*/
+		MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+
+		/*the "targetlist" of the updateact is filled in the parser */
+		updateact->relation = stmt->relation;
+		updateact->fromClause = stmt->source;
+		updateact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)updateact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_UPDATE|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper UPDATE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{
+		/*an insert action*/
+		Node *qual;
+		MergeInsert *insertact;
+
+		insertact = (MergeInsert *)(condact->action);
+		
+		
+		/*the "cols" and "selectStmt" of the insertact is filled in the parser */
+		insertact->relation = stmt->relation;
+
+		/*
+		the merge insert action has a strange feature. 
+		In an ordinary INSERT, the VALUES list can only contains constants and DEFAULT. (am I right??)
+		But in the INSERT action of MERGE command, the VALUES list can have expressions with variables(attributes of the targe and source tables).
+		Besides, in the ordinary INSERT, a VALUES list can never be followed by a WHERE clause. But in MERGE INSERT action, there are matching conditions. 
+
+		Thus, the output qry of this function is an INSERT query in the style of "INSERT...VALUES...", except that we have other range tables and a WHERE clause.
+		Note that it is also different from the "INSERT ... SELECT..." query, in which the whole SELECT is a subquery. (We don't have subquery here).
+		We construct this novel query structure in order to keep consitency with other merge action types (DELETE, UPDATE). 
+		In this way, all the merge action queries are in fact share the very same Range Table, They only differs in their target lists and join trees
+		
+		*/
+		
+				
+		/*parse the action query, this will call transformInsertStmt() which analyzes the VALUES list.*/
+		actqry = transformStmt(pstate, (Node *)insertact);
+
+		/*do the WHERE clause here, Since the transformInsertStmt() function only analyzes the VALUES list but not the WHERE clause*/
+		qual = transformWhereClause(pstate,(Node *)act_qual_expr, "WHERE");
+		
+		actqry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+		
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_INSERT|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper INSERT action in merge stmt");
+
+
+		return actqry;
+	}
+	else
+		elog(ERROR, "unknown action type in MERGE");
+
+	/*never comes here*/
+	return NULL;
+}
+
+
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+	Query	   *qry; 
+
+	ColumnRef *starRef;
+	ResTarget *starResTarget;
+	ListCell *act;
+	ListCell *l;
+	JoinExpr *joinexp;
+	int 	rtindex;
+
+	/*this will never happen, since the garm.y is restricted that only one rel name is allowed to appear in the source table position. 
+	However, if we extent the command in future, we may need to note this check here. 
+	*/
+	if(list_length(stmt->source) != 1) 
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("now we only accept merge command with only ONE source table")));
+		
+	/*now, do the real tranformation of the merge command. */
+	qry = makeNode(Query);
+	qry->commandType = CMD_MERGE;
+
+	/*
+	What we are doing here is to create a query like 
+		"SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+	Note:	
+	1. we set the "match condition" as the join qualification. 
+	The left join will scan both the matched and non-matched tuples.
+
+	2. a normal SELECT query has no "target relation". 
+	But here we need to set the targe relation in query, like the UPDATE/DELETE/INSERT queries. 
+	So this is a left join SELECT with a "target table" in its range table. 
+
+	3. We don't have a specific ACL level for Merge, here we just use ACL_SELECT. 
+	But we will add other ACL levels when handle each merge actions.  
+	*/
+
+
+	/*before analyze the FROM clause, we need to set the target table. 
+	We cannot call setTargetTable() function directly. 
+	We only need the lock target relation, without adding it to Range table. 
+	*/
+	
+	
+	setTargetTableLock(pstate, stmt->relation);
+	
+	
+	
+	/*create the FROM clause. Make the join expression first*/
+	joinexp = makeNode(JoinExpr);
+	joinexp->jointype = JOIN_LEFT;
+	joinexp->isNatural = FALSE;
+	joinexp->larg = linitial(stmt->source);/*source list has only one element*/
+	joinexp->rarg = (Node *)stmt->relation;
+	joinexp->quals = stmt->matchCondition; /*match condtion*/
+
+	/*transform the FROM clause. The target relation and source relation will be add to Rtable here.	*/
+	transformFromClause(pstate, list_make1(joinexp));
+
+	/*the targetList of the main query is "*"	  
+	*/
+	starRef = makeNode(ColumnRef);	
+	starRef->fields = list_make1(makeNode(A_Star));					
+	starRef->location = 1;					
+
+	starResTarget = makeNode(ResTarget);					
+	starResTarget->name = NULL;					
+	starResTarget->indirection = NIL;					
+	starResTarget->val = (Node *)starRef;					
+	starResTarget->location = 1;
+	
+	qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+	/*we don't need the WHERE clause here. Set it null. */
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	/*now , we find out the RTE for the target relation, and do some unfinished jobs*/
+	rtindex = 1;
+	foreach(l, pstate->p_rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+		if(rte->relid == pstate->p_target_relation->rd_id) /*find the RTE*/
+		{
+			pstate->p_target_rangetblentry = rte;
+			rte->requiredPerms = ACL_SELECT;	
+			qry->resultRelation = rtindex;
+			break;
+		}
+		rtindex++;
+	}
+
+	if(pstate->p_target_rangetblentry == NULL)
+		elog(ERROR, "cannot find the RTE for target table");
+	
+
+	qry->rtable = pstate->p_rtable;
+
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	/*
+	 * Top-level aggregates are simply disallowed in MERGE
+	 */
+	if (pstate->p_hasAggs)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("cannot use aggregate function in top level of MERGE"),
+				 parser_errposition(pstate,
+									locate_agg_of_level((Node *) qry, 0))));
+	if (pstate->p_hasWindowFuncs)
+		ereport(ERROR,
+				(errcode(ERRCODE_WINDOWING_ERROR),
+				 errmsg("cannot use window function in MERGE"),
+				 parser_errposition(pstate,
+									locate_windowfunc((Node *) qry))));
+
+
+
+
+	/*
+		the main query is done.
+	then for each actions ,we transform it to a seperate query.
+	the action queries shares the exactly same range table with the main query. 
+	in other words, in the extra condtions of the sub actions, we don't allow involvement of new tables
+	*/
+	
+	qry->mergeActQry = NIL;
+
+	foreach(act,stmt->actions)
+	{
+		MergeConditionAction *mca = lfirst(act);
+		Query *actqry;
+
+		switch(mca->action->type)
+		{
+			case T_MergeDelete:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+				break;
+			case T_MergeUpdate:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+				break;
+			case T_MergeInsert:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;				
+				break;
+			default:
+				elog(ERROR, "unknown MERGE action type %d", mca->type);
+				break;
+
+		}
+
+
+		/*transform the act (and its condition) as a single query. Link it to the top-level query*/
+		actqry = transformMergeActions(pstate, stmt, mca);
+
+		/*since we don't invoke setTargetTable() in transformMergeActions(), we need to set actqry->resultRelation here
+		*/
+		actqry->resultRelation = qry->resultRelation;
+
+
+		qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+	}
+
+
+	/*for a single-action merge, we just stransform it into a orignial update/delete command.
+	but the insert action cannot take this shortcut.
+	*/
+	/*
+	if(list_length(stmt->actions) == 1)
+	{
+		Query *q = linitial(qry->mergeActQry);
+		if(q->commandType == CMD_DELETE || q->commandType == CMD_UPDATE)
+			return q;
+	}
+	*/
+	return qry;
+
+	
+}
+
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..c139bd7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,12 @@ static TypeName *TableFuncTypeName(List *columns);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 
+%type <node>	MergeStmt  opt_and_condition  merge_condition_action   merge_action
+%type <boolean> opt_not	
+%type <list> 	merge_condition_action_list
+
+
+
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
 
@@ -233,7 +239,8 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <boolean>	opt_force opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
-
+				
+				
 %type <list>	OptRoleList AlterOptRoleList
 %type <defelt>	CreateOptRoleElem AlterOptRoleElem
 
@@ -504,6 +511,8 @@ static TypeName *TableFuncTypeName(List *columns);
 
 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
+	MATCHED MERGE
+	
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -725,6 +734,7 @@ stmt :
 			| ListenStmt
 			| LoadStmt
 			| LockStmt
+			| MergeStmt 
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -7297,6 +7307,101 @@ set_target_list:
 /*****************************************************************************
  *
  *		QUERY:
+ *				MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt: 
+			MERGE INTO relation_expr_opt_alias
+			USING  table_ref
+			ON a_expr
+			merge_condition_action_list
+				{
+					MergeStmt *m = makeNode(MergeStmt);
+
+					m->relation = $3;
+					m->source = list_make1($5);  /*although we have only one USING table, but we still make it a list, maybe in future we will allow mutliple USING tables.*/
+					m->matchCondition = $7;
+					m->actions = $8;
+
+					$$ = (Node *)m;
+				}
+				;
+	
+merge_condition_action_list: 
+							merge_condition_action 		
+								{ $$ = list_make1($1); }
+							| merge_condition_action_list merge_condition_action   
+								{ $$ = lappend($1,$2); }
+							;	
+
+merge_condition_action: 	
+							WHEN opt_not MATCHED opt_and_condition THEN merge_action
+							{
+								MergeConditionAction *m = makeNode(MergeConditionAction);
+
+								m->match = $2;
+								m->condition = $4;
+								m->action = $6;
+
+								$$ = (Node *)m;
+							}
+							;
+
+
+opt_and_condition:	
+					AND a_expr 		{$$ = $2;}
+					| /*EMPTY*/ 		{$$ = NULL;}
+					;
+
+opt_not:	
+			NOT			{$$ = false;}
+			| /*EMPTY*/	{$$ = true;}
+			;
+
+merge_action: 	
+				DELETE_P	
+					{$$ = (Node *)makeNode(MergeDelete);}
+				| UPDATE SET set_clause_list 
+					{
+						UpdateStmt *n = makeNode(MergeUpdate);
+						n->targetList = $3;
+						$$ = (Node *)n;
+					}
+				| INSERT values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = $2;
+
+						$$ = (Node *)n;
+					}
+					
+				| INSERT '(' insert_column_list ')' values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = $3;
+						n->selectStmt = $5;
+
+						$$ = (Node *)n;
+					}
+				| INSERT DEFAULT VALUES
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = NULL;
+
+						$$ = (Node *)n; 
+					}
+					
+				;	
+				
+
+
+/*****************************************************************************
+ *
+ *		QUERY:
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
@@ -10935,7 +11040,9 @@ unreserved_keyword:
 			| LOGIN_P
 			| MAPPING
 			| MATCH
+			| MATCHED
 			| MAXVALUE
+			| MERGE
 			| MINUTE_P
 			| MINVALUE
 			| MODE
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index f30132a..657dd9c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -213,6 +213,29 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	return rtindex;
 }
 
+
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+	
+	/* Close old target; this could only happen for multi-action rules */
+	if (pstate->p_target_relation != NULL)
+		heap_close(pstate->p_target_relation, NoLock);
+
+	/*
+	 * Open target rel and grab suitable lock (which we will hold till end of
+	 * transaction).
+	 *
+	 * free_parsestate() will eventually do the corresponding heap_close(),
+	 * but *not* release the lock.
+	 */
+	pstate->p_target_relation = parserOpenTable(pstate, relation,
+												RowExclusiveLock);
+
+	
+}
+
+
 /*
  * Simplify InhOption (yes/no/default) into boolean yes/no.
  *

diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..287fa1f 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1837,6 +1837,45 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 }
 
 
+#define insert_rewrite (1<<0) /*insert merge action has already been processed by rewriter*/
+#define delete_rewrite (1<<1) 
+#define update_rewrite (1<<2)
+
+#define	insert_instead (1<<3) /*insert merge action is fully replace by rules.*/
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+
+#define merge_action_already_rewrite(acttype, flag) \
+	((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+		(acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+		(acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+
+#define set_action_rewrite(acttype, flag)	\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_rewrite;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_rewrite;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_rewrite;}
+
+
+				
+#define merge_action_instead(acttype, flag)		\
+			((acttype == CMD_INSERT && (flag & insert_instead)) || \
+				(acttype == CMD_UPDATE && (flag & update_instead)) || \
+				(acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_instead;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_instead;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_instead;}
+
+
 /*
  * QueryRewrite -
  *	  Primary entry point to the query rewriter.
@@ -1861,7 +1900,133 @@ QueryRewrite(Query *parsetree)
 	 *
 	 * Apply all non-SELECT rules possibly getting 0 or many queries
 	 */
-	querylist = RewriteQuery(parsetree, NIL);
+
+	
+	if(parsetree->commandType == CMD_MERGE)
+	{
+		/*for merge query, we have a set of lower-level action queries (not subquery).
+		//each of these action queries should be applied to RewriteQuery(). 
+		*/
+		ListCell   *cell,
+				   *prev,
+				   *next;
+
+		int flag = 0;
+
+		
+		List *pre_qry = NIL;
+		List *post_qry = NIL;
+
+
+		querylist = NIL;
+		
+
+		/*rewrite the merge action queries one by one.*/
+		prev = NULL;
+
+		for (cell = list_head(parsetree->mergeActQry); cell; cell = next)
+		{
+			List *queryList4action = NIL;
+			Query *actionqry;
+			Query *q;
+
+			
+			actionqry = lfirst(cell);
+
+			next = lnext(cell);
+
+			/*if this kind of actions are fully replaced by rules, we delete it from the action list*/	
+			if(merge_action_instead(actionqry->commandType, flag))
+			{
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+
+			/*if this kind of actions are already processed by rewriter, skip it.*/
+			if(merge_action_already_rewrite(actionqry->commandType, flag))
+			{
+
+			
+				prev = cell; 
+				continue;
+			}
+
+			/*ok this action has not been processed before, let's do it now.*/
+			
+			queryList4action = RewriteQuery(actionqry, NIL);
+			set_action_rewrite(actionqry->commandType,flag); /*this kind of actions has been processed.*/
+
+			/*if the returning list is nil, this merge action is replaced by a do-nothing rule*/
+			if(queryList4action == NIL) 
+			{
+				/*set the flag for other merge actions of the same type*/
+				set_action_instead(actionqry->commandType, flag);
+				/*delete the action.*/
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+			
+			/*the merge action query could be one of the elements in the rewriten list. 
+			//if it is in the list, it must be the head or tail.
+			*/
+			q = (Query *)linitial(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the head, the remaining part of the list are the queries generated by rules
+				//we put them in the post_qry list. 
+				*/
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+
+
+				queryList4action = list_delete_first(queryList4action);
+				post_qry = list_concat(post_qry,queryList4action);
+				prev = cell; 
+				continue;
+
+			}
+			
+			
+			q = (Query *)llast(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the tail. Put the rule queries in pre_qry list*/
+
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+					
+				queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+				pre_qry = list_concat(pre_qry,queryList4action);
+
+				prev = cell; 
+				continue;
+
+			}	
+				
+
+			/*here, the merge action query is not in the rewriten query list, which means the action should be deleted
+			//It is replaced by INSTEAD rule(s). We need to delete the action  
+			*/
+			post_qry = list_concat(post_qry,queryList4action);	
+			set_action_instead(actionqry->commandType, flag);
+			parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+		}				
+
+		
+
+		/*finally, put the 3 lists into one. 
+		If all the merge actions are replaced by rules, the original merge query 
+		will not be involved in the querylist.
+		*/
+		querylist = list_concat(pre_qry,querylist);
+		querylist = list_concat(querylist, post_qry);
+			
+	}
+	else
+		querylist = RewriteQuery(parsetree, NIL);
 
 	/*
 	 * Step 2
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5efad23..9a9bc7c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1012,6 +1012,7 @@ exec_simple_query(const char *query_string)
 		 */
 		PortalStart(portal, NULL, InvalidSnapshot);
 
+
 		/*
 		 * Select the appropriate output format: text unless we are doing a
 		 * FETCH from a binary cursor.	(Pretty grotty to have to do this here
@@ -1046,6 +1047,7 @@ exec_simple_query(const char *query_string)
 		 */
 		MemoryContextSwitchTo(oldcontext);
 
+
 		/*
 		 * Run the portal to completion, and then drop it (and the receiver).
 		 */
@@ -1056,6 +1058,7 @@ exec_simple_query(const char *query_string)
 						 receiver,
 						 completionTag);
 
+
 		(*receiver->rDestroy) (receiver);
 
 		PortalDrop(portal, false);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..4453b47 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -191,6 +191,7 @@ ProcessQuery(PlannedStmt *plan,
 	 */
 	ExecutorStart(queryDesc, 0);
 
+
 	/*
 	 * Run the plan to completion.
 	 */
@@ -225,6 +226,10 @@ ProcessQuery(PlannedStmt *plan,
 				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
 						 "DELETE %u", queryDesc->estate->es_processed);
 				break;
+			case CMD_MERGE:
+				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+						 "MERGE %u", queryDesc->estate->es_processed);
+				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8960246..2733e5d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
 			case CMD_UPDATE:
 			case CMD_INSERT:
 			case CMD_DELETE:
+			case CMD_MERGE:
 				return false;
 			default:
 				elog(WARNING, "unrecognized commandType: %d",
@@ -1398,6 +1399,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_MergeStmt:
+			tag = "MERGE";
+			break;
+		
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2235,6 +2240,7 @@ GetCommandLogLevel(Node *parsetree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
+		case T_MergeStmt:
 			lev = LOGSTMT_MOD;
 			break;
 
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..7ae2f0a 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,9 @@
 #include "nodes/execnodes.h"
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+
+extern PlanState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
+
 extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 09fdb5d..8147c44 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1031,6 +1031,7 @@ typedef struct ModifyTableState
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
+	List 		*mergeActPstates; /*the list of the panstate of each meger command action. NIL if this is not a merge command, all actions' */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..15d11a0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
 	T_Plan = 100,
 	T_Result,
 	T_ModifyTable,
+	T_MergeAction,
 	T_Append,
 	T_RecursiveUnion,
 	T_BitmapAnd,
@@ -261,6 +262,15 @@ typedef enum NodeTag
 	T_DeleteStmt,
 	T_UpdateStmt,
 	T_SelectStmt,
+
+	
+	T_MergeStmt,
+	T_MergeConditionAction,
+	T_MergeUpdate, 
+	T_MergeDelete,  
+	T_MergeInsert,
+
+	
 	T_AlterTableStmt,
 	T_AlterTableCmd,
 	T_AlterDomainStmt,
@@ -511,6 +521,7 @@ typedef enum CmdType
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
 	CMD_DELETE,
+	CMD_MERGE,						/*merge stmt*/
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b591073..d6de83c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -146,6 +146,11 @@ typedef struct Query
 
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
+
+	bool		 isMergeAction; /*if this query is a merge action. */	 
+
+	List 		*mergeActQry; /* the list of all the merge actions. 
+								* used only for merge query statment*/							 
 } Query;
 
 
@@ -990,6 +995,30 @@ typedef struct SelectStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
+/*ZBX: the structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+	NodeTag		type;
+	RangeVar   	*relation;		/*targe relation for merge */
+	List		*source;		/* source relations for the merge. Currently, we only allwo single-source merge, so the length of this list should always be 1 */
+	Node	   	*matchCondition;	/* qualifications of the merge*/
+	List	   	*actions;		/*list  of MergeConditionAction structure. It stores all the match / non-matching conditions and the corresponding actions*/
+
+}MergeStmt;
+
+/*the structure for the actions of MERGE command. Holds info of the clauses like "... WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+	NodeTag		type;
+	bool 		match; /*match or not match*/
+	Node		*condition;/*the AND condition for this action*/
+	Node 		*action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
 
 /* ----------------------
  *		Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..4e067b2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -66,6 +66,7 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -169,8 +170,16 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List 		*mergeActPlan;	/*the plans for merge actions, which are also ModifyTable nodes*/
 } ModifyTable;
 
+
+typedef struct MergeAction
+{
+	Plan plan;
+	CmdType	operation;/* INSERT, UPDATE, or DELETE */
+}MergeAction;
+
 /* ----------------
  *	 Append node -
  *		Generate the concatenation of the results of sub-plans.
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 805dee7..6d7ff4f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -34,6 +34,7 @@ extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
 				 bool hasRecursion, double tuple_fraction,
 				 PlannerInfo **subroot);
+extern ModifyTable *merge_action_planner(PlannerGlobal *glob, Query *parse, Plan *top_plan);
 
 extern Expr *expression_planner(Expr *expr);
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..436d459 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -229,7 +229,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..e4312d8 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,8 @@
 extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
+
 extern bool interpretInhOption(InhOption inhOpt);
 extern bool interpretOidsOption(List *defList);
 
diff --git a/src/include/port.h b/src/include/port.h
index 291a3e7..93fb00a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -152,7 +152,7 @@ extern unsigned char pg_toupper(unsigned char ch);
 extern unsigned char pg_tolower(unsigned char ch);
 
 #ifdef USE_REPL_SNPRINTF
-
+#define   zbxprint   printf
 /*
  * Versions of libintl >= 0.13 try to replace printf() and friends with
  * macros to their own versions that understand the %$ format.	We do the
#2Robert Haas
robertmhaas@gmail.com
In reply to: Boxuan Zhai (#1)
Re: merge command - GSoC progress

On Tue, Jul 27, 2010 at 1:04 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

I have get a edition that the merge command can run. It accept the standard
merge command and can do UPDATE, INSERT and DELETE actions now. But we
cannot put additional qualification for actions. There are some bugs when we
try to evaluate the quals which make the system quit. I will fix it soon.

This patch doesn't compile. You're using zbxprint() from a bunch of
places where it's not defined. I get compile warnings for all of
those files and then a link failure at the end. You might find it
useful to create src/Makefile.custom in your local tree and put
COPT=-Werror in there; it tends to prevent problems of this kind.

Undefined symbols:
"_zbxprint", referenced from:
_transformStmt in analyze.o
_ExecInitMergeAction in nodeModifyTable.o
_ExecModifyTable in nodeModifyTable.o
_ExecInitModifyTable in nodeModifyTable.o
_merge_action_planner in planner.o

Not that it's as high-priority as getting this fully working, but you
should revert the useless changes in this patch - e.g. the one-line
change to heaptuple.c is obvious debugging leftovers, and all of the
changes to execQual.c and execUtil.c are whitespace-only. You should
also try to make your code and comments conform to project style
guidelines. In general, you'll find it easier to keep track of your
changes (and you'll have fewer spurious changes) if you use git diff
master...yourbranch instead of marking comments, etc. with ZBX or
similar.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#3Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Robert Haas (#2)
Re: merge command - GSoC progress

2010/7/28 Robert Haas <robertmhaas@gmail.com>

On Tue, Jul 27, 2010 at 1:04 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

I have get a edition that the merge command can run. It accept the

standard

merge command and can do UPDATE, INSERT and DELETE actions now. But we
cannot put additional qualification for actions. There are some bugs when

we

try to evaluate the quals which make the system quit. I will fix it soon.

This patch doesn't compile. You're using zbxprint() from a bunch of
places where it's not defined. I get compile warnings for all of
those files and then a link failure at the end. You might find it
useful to create src/Makefile.custom in your local tree and put
COPT=-Werror in there; it tends to prevent problems of this kind.

Undefined symbols:
"_zbxprint", referenced from:
_transformStmt in analyze.o
_ExecInitMergeAction in nodeModifyTable.o
_ExecModifyTable in nodeModifyTable.o
_ExecInitModifyTable in nodeModifyTable.o
_merge_action_planner in planner.o

Sorry, this is a debug function defined by me. It may not be included in

the patch. I add a line of "#define zbxprint printf" somewhere in the
system.

Not that it's as high-priority as getting this fully working, but you
should revert the useless changes in this patch - e.g. the one-line
change to heaptuple.c is obvious debugging leftovers, and all of the
changes to execQual.c and execUtil.c are whitespace-only. You should
also try to make your code and comments conform to project style
guidelines. In general, you'll find it easier to keep track of your
changes (and you'll have fewer spurious changes) if you use git diff
master...yourbranch instead of marking comments, etc. with ZBX or
similar.

I will clean all these in my next patch.

I am now very confused with the failure of action qualification. I look
through the whole process of a query, from parser to executor. In my
opinion, the qualification transformed in analyzer, will be processed by
prepsocess_qual_condition() in planner, and then by the ExecInitExpr()
function in excutor start phase (in InitPlan() function). Then the qual is
ready to be used in ExecQual(). Am I correct?

I have done these on the merge action qual, but when I pass them into
ExecQual(), the server just closed abnormally. I don't know if I missed any
steps on preparing the qual expressions.

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#4Robert Haas
robertmhaas@gmail.com
In reply to: Boxuan Zhai (#3)
Re: merge command - GSoC progress

On Wed, Jul 28, 2010 at 6:08 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

On Tue, Jul 27, 2010 at 1:04 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

I have get a edition that the merge command can run. It accept the
standard
merge command and can do UPDATE, INSERT and DELETE actions now. But we
cannot put additional qualification for actions. There are some bugs
when we
try to evaluate the quals which make the system quit. I will fix it
soon.

This patch doesn't compile.  You're using zbxprint() from a bunch of
places where it's not defined.  I get compile warnings for all of
those files and then a link failure at the end.  You might find it
useful to create src/Makefile.custom in your local tree and put
COPT=-Werror in there; it tends to prevent problems of this kind.

Undefined symbols:
 "_zbxprint", referenced from:
     _transformStmt in analyze.o
     _ExecInitMergeAction in nodeModifyTable.o
     _ExecModifyTable in nodeModifyTable.o
     _ExecInitModifyTable in nodeModifyTable.o
     _merge_action_planner in planner.o

Sorry, this is a debug function defined by me. It may not be included in the
patch. I add a line of "#define zbxprint printf" somewhere in the system.

Yeah, but it's not included in all the places that are needed to make
everything compile. You might move this to postgres.h or something.

Not that it's as high-priority as getting this fully working, but you
should revert the useless changes in this patch - e.g. the one-line
change to heaptuple.c is obvious debugging leftovers, and all of the
changes to execQual.c and execUtil.c are whitespace-only.  You should
also try to make your code and comments conform to project style
guidelines.  In general, you'll find it easier to keep track of your
changes (and you'll have fewer spurious changes) if you use git diff
master...yourbranch instead of marking comments, etc. with ZBX or
similar.

I will clean all these in my next patch.

I am now very confused with the failure of action qualification. I look
through the whole process of a query, from parser to executor. In my
opinion, the qualification transformed in analyzer, will be processed by
prepsocess_qual_condition() in planner, and then by the ExecInitExpr()
function in excutor start phase (in InitPlan() function). Then the qual is
ready to be used in ExecQual(). Am I correct?

I'm not sure, sorry.

I have done these on the merge action qual, but when I pass them into
ExecQual(), the server just closed abnormally. I don't know if I missed any
steps on preparing  the qual expressions.

Have you tried attaching a debugger? Try "SELECT pg_backend_pid()"
and then use "gdb -p the_pid" from another window. Hit "continue".
Then do whatever it is that's crashing. That way you can get a stack
backtrace, and poke around at the data structures.

Using pprint() on node-type data structures, either in debugging code
or actually straight from the debugger via "call", is also very
helpful, often-times.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#5Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Robert Haas (#4)
1 attachment(s)
Re: merge command - GSoC progress

Hi,

I have fixed the action qual problem. Now the system can run merge command,
with quals.

I create a clean patch file (no debug clauses). See the attachment.

Please try this new command if you have interest.

Boxuan

Attachments:

merge_no_debug.patchtext/plain; charset=US-ASCII; name=merge_no_debug.patchDownload
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2f33fdb..56c13ee 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,6 +171,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_INSERT:
 		case CMD_DELETE:
 		case CMD_UPDATE:
+		case CMD_MERGE:	
 			estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_MergeAction:
+			result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+													   estate, eflags);
+			break;
+			
 		case T_Append:
 			result = (PlanState *) ExecInitAppend((Append *) node,
 												  estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..c549d7e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -583,6 +583,116 @@ lreplace:;
 }
 
 
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   ModifyTableState *node,
+		   EState *estate)
+{
+
+	TupleTableSlot *actslot = NULL;
+	TupleTableSlot *res = NULL;
+	ListCell *each;
+
+	/*
+	*	try the merge actions one by one
+	*/
+	foreach(each, node->mergeActPstates)
+	{
+		ModifyTableState *actpstate;
+
+		PlanState *pstate; 
+
+		ExprContext *econtext;
+
+		
+		actpstate = (ModifyTableState *)lfirst(each);
+		
+		Assert(actpstate->mt_nplans == 1);
+
+		pstate = actpstate->mt_plans[0];
+
+		/*
+		*	If tupleid == NULL, it is a NOT MATCHED case, in which we can only do INSERT action
+		*	If tupleid != NULL, it is a MATCHED case, in which we can only do UPDATE or DELETE action
+		*/
+		
+		if((tupleid == NULL && actpstate->operation != CMD_INSERT) ||
+			(tupleid != NULL && actpstate->operation == CMD_INSERT))
+		{
+			continue;
+		}
+
+		/*
+		*	Setup the expression context
+		*/
+		econtext = pstate->ps_ExprContext;
+
+		/*
+		If the action has an additional qual, which is not satisfied, skip it
+		*/	
+		
+		if(pstate->qual) 
+		{	
+
+			bool qualres;
+			
+			ResetExprContext(econtext);
+		
+			econtext->ecxt_scantuple = slot;
+			econtext->ecxt_outertuple = planSlot;
+
+			 if(!ExecQual(pstate->qual, econtext,false))
+				continue;
+		}
+
+		/*
+		*	Now we start to exec this action. Firstly , project the result tuple slot. 
+		*	The merge action result slot is projected in the same way as a returning slot. 
+		*	so we call ExecProcessReturning() directly here.
+		*/
+		if(actpstate->operation != CMD_DELETE)
+			actslot = ExecProcessReturning(pstate->ps_ProjInfo, slot, planSlot);
+	
+			
+		switch (actpstate->operation)
+		{
+			case CMD_INSERT:
+				res = ExecInsert(actslot, planSlot, estate);
+				break;
+			case CMD_UPDATE:
+				res = ExecUpdate(tupleid,
+							actslot,
+							planSlot,
+							&actpstate->mt_epqstate,
+							estate);
+				break;
+			case CMD_DELETE:
+				res = ExecDelete(tupleid, 
+							planSlot, 
+							&actpstate->mt_epqstate,
+							estate);
+				break;
+			default:
+				elog(ERROR, "unknown merge action type");
+				break;
+		}
+
+
+		/*
+		* The current action is executed, no need to do the remain actions
+		*/	
+		return res;
+	}
+
+	/*
+	* Here, no action is taken. Let's pass this slot
+	*/	
+	return NULL;
+	
+}
+
 /*
  * Process BEFORE EACH STATEMENT triggers
  */
@@ -603,6 +713,9 @@ fireBSTriggers(ModifyTableState *node)
 			ExecBSDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the BS triggers of merge\n");
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -629,6 +742,9 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the AS triggers of merge\n");
+			break;	
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -708,20 +824,34 @@ ExecModifyTable(ModifyTableState *node)
 			/*
 			 * extract the 'ctid' junk attribute.
 			 */
-			if (operation == CMD_UPDATE || operation == CMD_DELETE)
+			if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 			{
 				Datum		datum;
 				bool		isNull;
 
 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
 											 &isNull);
-				/* shouldn't ever get a null result... */
+				
 				if (isNull)
-					elog(ERROR, "ctid is NULL");
-
-				tupleid = (ItemPointer) DatumGetPointer(datum);
-				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
-				tupleid = &tuple_ctid;
+				{
+					/*
+					*	shouldn't ever get a null result for update and delete. 
+					*	Merge command will get a null ctid if in "NOT MATCHED" case
+					*/
+					if(operation != CMD_MERGE)
+						elog(ERROR, "ctid is NULL");
+					else
+						tupleid = NULL;
+				}	
+				else
+				{
+				
+					tupleid = (ItemPointer) DatumGetPointer(datum);
+					
+					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
+					tupleid = &tuple_ctid;
+					
+				}
 			}
 
 			/*
@@ -744,6 +874,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecDelete(tupleid, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
+			case CMD_MERGE:
+				slot = ExecMerge(tupleid, slot, planSlot,
+								  node, estate);
+				break;	
+				
 			default:
 				elog(ERROR, "unknown operation");
 				break;
@@ -771,6 +906,93 @@ ExecModifyTable(ModifyTableState *node)
 	return NULL;
 }
 
+
+
+
+
+
+
+
+/*
+*	When init a merge plan, we also need init its action plans. 
+*	However, these action plans are "pure" plans. We only want to handle the tlist and qual
+*	Thus, the return result is a plain "PlanState". We dont have a MeregeActionState kind of thing.
+*/
+PlanState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+	PlanState *result;
+	
+	/*
+	 * do nothing when we get to the end of a leaf on tree.
+	 */
+	if (node == NULL)
+		return NULL;
+
+		
+	/*
+	 * create state structure
+	 */
+	result = makeNode(PlanState);
+	result->plan = (Plan *)node;
+	result->state = estate;
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, result);
+
+
+	
+	/*
+	 * initialize tuple type and projection info
+	 */
+	ExecAssignResultTypeFromTL(result);
+
+		
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	 
+	ExecAssignExprContext(estate, result);
+
+
+	/*
+	 * initialize child expressions
+	 */
+	result->targetlist = (List *)
+		ExecInitExpr((Expr *) node->plan.targetlist,  result);
+
+	
+	result->qual = (List *)
+		ExecInitExpr((Expr *) node->plan.qual, result);
+
+	
+	/*
+	* init the projection information
+	*/
+	ExecAssignProjectionInfo(result, NULL);
+
+/*
+printf("before check plan output for merge actio \n");	
+if(estate->es_result_relation_info == NULL)
+	printf("re infro is null\n");
+
+if(estate->es_result_relation_info->ri_RelationDesc == NULL)
+	printf("r relation desc is nUll\n");
+	ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+							node->targetlist);
+printf("AFTER check plan output for merge actio \n");	
+*/
+	return result;
+}
+
+
+
+
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -786,7 +1008,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	bool 		isMergeAction = false;
 
+	
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -797,7 +1021,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 */
 	if (estate->es_epqTuple != NULL)
 		elog(ERROR, "ModifyTable should not be called during EvalPlanQual");
-
 	/*
 	 * create state structure
 	 */
@@ -826,12 +1049,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+		if(IsA(subplan, MergeAction))
+			isMergeAction = true;
+		
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+
 		estate->es_result_relation_info++;
 		i++;
 	}
+	
 	estate->es_result_relation_info = NULL;
 
+	
 	/* select first subplan */
 	mtstate->mt_whichplan = 0;
 	subplan = (Plan *) linitial(node->plans);
@@ -955,7 +1184,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
-				junk_filter_needed = true;
+			case CMD_MERGE:	
+				if(!isMergeAction)
+					junk_filter_needed = true;
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -973,15 +1204,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
 					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
 										subplan->targetlist);
-
+				
 				j = ExecInitJunkFilter(subplan->targetlist,
 							resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
 									   ExecInitExtraTupleSlot(estate));
 
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
+				if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 				{
 					/* For UPDATE/DELETE, find the ctid junk attr now */
 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+
 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
 						elog(ERROR, "could not find junk ctid column");
 				}
@@ -1006,6 +1238,21 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (estate->es_trig_tuple_slot == NULL)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	
+	/*
+	* for the merge actions, we need to similar things as above
+	*/
+
+	foreach(l, node->mergeActPlan)
+	{
+		PlanState *actpstate = ExecInitNode((Plan *)lfirst(l),  estate, 0);
+		/*
+		* put the pstates of each action into ModifyTableState
+		*/
+		mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+		
+	}
+	
 	return mtstate;
 }
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 93dcef5..f0be5e8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(mergeActPlan);
 
 	return newnode;
 }
@@ -2273,6 +2274,10 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 
+	COPY_SCALAR_FIELD(isMergeAction);
+	/*merge action list*/
+	COPY_NODE_FIELD(mergeActQry); 
+
 	return newnode;
 }
 
@@ -2343,6 +2348,64 @@ _copySelectStmt(SelectStmt *from)
 	return newnode;
 }
 
+
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+	MergeStmt *newnode = makeNode(MergeStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(source);
+	COPY_NODE_FIELD(matchCondition);
+	COPY_NODE_FIELD(actions);
+	
+	return newnode;
+	
+}
+
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+	MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+	COPY_SCALAR_FIELD(match);
+	COPY_NODE_FIELD(condition);
+	COPY_NODE_FIELD(action);
+
+	return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+	MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+	newNode->type = T_MergeUpdate;
+
+	return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+	MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+	newNode->type = T_MergeInsert;
+
+	return newNode;
+}
+
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+	MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+	newNode->type = T_MergeDelete;
+
+	return newNode;
+}
+
+
+
 static SetOperationStmt *
 _copySetOperationStmt(SetOperationStmt *from)
 {
@@ -3902,7 +3965,22 @@ copyObject(void *from)
 			break;
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
-			break;
+			break;		
+		case T_MergeStmt:
+			retval = _copyMergeStmt(from);
+			break;			
+		case T_MergeConditionAction:
+			retval = _copyMergeConditionAction(from);
+			break;	
+		case T_MergeUpdate:
+			retval = _copyMergeUpdate(from);
+			break;
+		case T_MergeInsert:
+			retval = _copyMergeInsert(from);
+			break;	
+		case T_MergeDelete:
+			retval = _copyMergeDelete(from);
+			break;	
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5d83727..572f6df 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,6 +878,7 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
 
+	COMPARE_SCALAR_FIELD(isMergeAction);
 	return true;
 }
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 79baf4f..4b3dd60 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(mergeActPlan);
 }
 
 static void
@@ -2019,9 +2020,52 @@ _outQuery(StringInfo str, Query *node)
 	WRITE_NODE_FIELD(limitCount);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(setOperations);
+	WRITE_BOOL_FIELD(isMergeAction);
+	WRITE_NODE_FIELD(mergeActQry);
+}
+
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+	WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+	WRITE_BOOL_FIELD(match);
+	
+	WRITE_NODE_FIELD(condition);
+	WRITE_NODE_FIELD(action);
+
+
 }
 
 static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+	WRITE_NODE_TYPE("MERGESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(source);
+	WRITE_NODE_FIELD(matchCondition);
+	WRITE_NODE_FIELD(actions);
+
+}
+
+static void 
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+	WRITE_NODE_TYPE("DELETESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(usingClause);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_NODE_FIELD(returningList);
+
+
+}
+
+
+
+static void
 _outSortGroupClause(StringInfo str, SortGroupClause *node)
 {
 	WRITE_NODE_TYPE("SORTGROUPCLAUSE");
@@ -2905,6 +2949,17 @@ _outNode(StringInfo str, void *obj)
 				_outXmlSerialize(str, obj);
 				break;
 
+			case T_MergeStmt:
+				_outMergeStmt(str, obj);
+				break;
+			case T_MergeConditionAction:
+				_outMergeConditionAction(str,obj);
+				break;
+			case T_DeleteStmt:
+				_outDeleteStmt(str,obj);
+				break;
+					
+
 			default:
 
 				/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..f8ab23f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -565,6 +565,26 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											 returningLists,
 											 rowMarks,
 											 SS_assign_special_param(root));
+
+			/*do a simple plan for each actions in the merge command.
+			*put them in mergeActPlan list;
+			*/
+			if(parse->commandType == CMD_MERGE)
+			{
+				ListCell *l;
+				ModifyTable *top_plan = (ModifyTable *)plan;
+				foreach(l, parse->mergeActQry)
+				{
+					
+					Plan *actplan = (Plan *)merge_action_planner(glob, 
+																		(Query *)lfirst(l),
+																		(Plan *)linitial(top_plan->plans)
+																	);
+
+					top_plan->mergeActPlan = lappend(top_plan->mergeActPlan, actplan);
+				}
+
+			}
 		}
 	}
 
@@ -584,6 +604,107 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	return plan;
 }
 
+
+
+ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+				 Plan *top_plan)
+{
+	PlannerInfo *root;
+	MergeAction	*actplan;
+	ModifyTable *result;
+	
+	List	   	*returningLists;
+	List 		*rowMarks;
+
+	/* Create a PlannerInfo data structure for this subquery */
+	root = makeNode(PlannerInfo);
+	root->parse = parse;
+	root->glob = glob;
+	root->query_level = 1;
+	root->parent_root = NULL;
+	root->planner_cxt = CurrentMemoryContext;
+	root->init_plans = NIL;
+	root->cte_plan_ids = NIL;
+	root->eq_classes = NIL;
+	root->append_rel_list = NIL;
+
+	root->hasRecursion = false;
+	root->wt_param_id = -1;
+	root->non_recursive_plan = NULL;
+
+
+	/*
+	 * no having clause in a merge action
+	 */
+	Assert(parse->havingQual == NULL);
+
+	/* Clear this flag; might get set in distribute_qual_to_rels */
+	root->hasPseudoConstantQuals = false;
+
+	/*
+	 * Do expression preprocessing on targetlist and quals.
+	 */
+
+	
+	parse->targetList = (List *)
+		preprocess_expression(root, (Node *) parse->targetList,
+							  EXPRKIND_TARGET);
+
+	preprocess_qual_conditions(root, (Node *) parse->jointree);
+
+	/*
+	 * Do the main planning.  If we have an inherited target relation, that
+	 * needs special processing, else go straight to grouping_planner.
+	 */
+
+
+	actplan = makeNode(MergeAction);
+	actplan->operation = parse->commandType;
+
+	/*copy the cost from the top_plan*/
+	actplan->plan.startup_cost = top_plan->startup_cost;
+	actplan->plan.total_cost = top_plan->total_cost;
+	actplan->plan.plan_rows = top_plan->plan_rows;
+	actplan->plan.plan_width = top_plan->plan_width;
+	
+	
+	actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+	actplan->plan.qual = (List *)parse->jointree->quals;
+	
+	if (parse->returningList)
+	{
+		List	   *rlist;
+
+		Assert(parse->resultRelation);
+		rlist = set_returning_clause_references(root->glob,
+												parse->returningList,
+												&actplan->plan,
+											  parse->resultRelation);
+		returningLists = list_make1(rlist);
+	}
+	else
+		returningLists = NIL;
+
+
+	if (parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
+	
+	result = make_modifytable(parse->commandType,
+										   copyObject(root->resultRelations),
+											 list_make1(actplan),
+											 returningLists,
+											 rowMarks,
+											 SS_assign_special_param(root));
+
+	
+	return result;
+}
+
+
 /*
  * preprocess_expression
  *		Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..5685d52 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 								  result_relation, range_table);
 
 	/*
-	 * for "update" and "delete" queries, add ctid of the result relation into
+	 * for "update" , "delete"  and "merge" queries, add ctid of the result relation into
 	 * the target list so that the ctid will propagate through execution and
 	 * ExecutePlan() will be able to identify the right tuple to replace or
 	 * delete.	This extra field is marked "junk" so that it is not stored
 	 * back into the tuple.
+	 *  
+	 * BUT, if the query node is a merge action, we don't need to expend the ctid attribute in tlist.
+	 * The tlist of the merge top level plan already contains a "ctid" junk attr of the target relation.
 	 */
-	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+	 
+	if(!parse->isMergeAction  &&  
+			(command_type == CMD_UPDATE || 
+			command_type == CMD_DELETE || 
+			command_type == CMD_MERGE))
 	{
 		TargetEntry *tle;
 		Var		   *var;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..89fe1e3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,8 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
 static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
 static List *transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos);
+
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +166,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			 * Optimizable statements
 			 */
 		case T_InsertStmt:
+		case T_MergeInsert:
 			result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
 			break;
 
 		case T_DeleteStmt:
+		case T_MergeDelete:
 			result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
 			break;
 
 		case T_UpdateStmt:
+		case T_MergeUpdate:
 			result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
 			break;
 
+		case T_MergeStmt:
+			result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+			break; 
+
 		case T_SelectStmt:
 			{
 				SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +291,33 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	qry->commandType = CMD_DELETE;
 
-	/* set up range table with just the result rel */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_DELETE);
-
 	qry->distinctClause = NIL;
 
+	if(IsA(stmt,MergeDelete))
+		qry->isMergeAction = true;
 	/*
-	 * The USING clause is non-standard SQL syntax, and is equivalent in
-	 * functionality to the FROM list that can be specified for UPDATE. The
-	 * USING keyword is used rather than FROM because FROM is already a
-	 * keyword in the DELETE syntax.
-	 */
-	transformFromClause(pstate, stmt->usingClause);
+	* The input stmt could be a MergeDelete node. 
+	* In this case, we don't need the process on range table.
+	*/
+	if(IsA(stmt, DeleteStmt))
+	{
+		/* set up range table with just the result rel */
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_DELETE);
+
+		
 
+		/*
+		 * The USING clause is non-standard SQL syntax, and is equivalent in
+		 * functionality to the FROM list that can be specified for UPDATE. The
+		 * USING keyword is used rather than FROM because FROM is already a
+		 * keyword in the DELETE syntax.
+		 */
+		transformFromClause(pstate, stmt->usingClause);
+	}
+	
 	qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -342,11 +362,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->commandType = CMD_INSERT;
 	pstate->p_is_insert = true;
 
+	if(IsA(stmt,MergeInsert))
+		qry->isMergeAction = true;
+	
 	/*
 	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
 	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
 	 * efficiency and so we can handle DEFAULT specifications.
 	 */
+
+	/*a MergeInsert statment is always a VALUE clause*/
 	isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
 	/*
@@ -382,7 +407,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * mentioned in the SELECT part.  Note that the target table is not added
 	 * to the joinlist or namespace.
 	 */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
+	if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/ 
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
 										 false, false, ACL_INSERT);
 
 	/* Validate stmt->cols list, or build default list if no list given */
@@ -799,6 +825,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -1730,16 +1757,23 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_update = true;
 
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_UPDATE);
+	if(IsA(stmt,MergeUpdate))
+		qry->isMergeAction = true;
 
-	/*
-	 * the FROM clause is non-standard SQL syntax. We used to be able to do
-	 * this with REPLACE in POSTQUEL so we keep the feature.
-	 */
-	transformFromClause(pstate, stmt->fromClause);
+	
+	if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+	{
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_UPDATE);
+
+		/*
+		 * the FROM clause is non-standard SQL syntax. We used to be able to do
+		 * this with REPLACE in POSTQUEL so we keep the feature.
+		 */
+		transformFromClause(pstate, stmt->fromClause);
+	}
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -2241,3 +2275,336 @@ applyLockingClause(Query *qry, Index rtindex,
 	rc->pushedDown = pushedDown;
 	qry->rowMarks = lappend(qry->rowMarks, rc);
 }
+
+
+
+/*transform a action of merge command into a query. 
+No change of the pstate range table is allowed in this function. 
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+	Query *actqry = makeNode(Query);
+	A_Expr *match_expr; //the expr of matched/not matched
+	A_Expr *act_qual_expr;
+
+	/*firstly, we need to make sure that DELETE and UPDATE actions are only taken in MATCHED condition 
+	and INSERTs are only takend when not MATCHED
+	*/
+	if(IsA(condact->action, MergeDelete)) 
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{	
+		if(condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+	}
+	else
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("UNKONWN action type in MERGE")));
+
+
+		
+	/*combine the condtion of this act with the ON qual of the merge command
+	do a copy of the merge condtion for safety. 
+	*/
+
+	/*
+	if(condact->match) 
+		match_expr = copyObject(stmt->matchCondition);
+	else
+		match_expr = makeA_Expr(AEXPR_NOT, NIL, NULL, 
+									copyObject(stmt->matchCondition), 1);
+											
+
+	if(condact->condition)
+		act_qual_expr = makeA_Expr(AEXPR_AND, NIL, condact->condition, (Node *)match_expr, 2);
+	else
+		act_qual_expr = match_expr;
+
+	*/
+
+	act_qual_expr = condact->condition;
+	
+	/*use the transfomStmt() to parse all types of actions*/
+	if(IsA(condact->action, MergeDelete))
+	{
+		/*a delete action*/
+		MergeDelete *deleteact = (MergeDelete *)(condact->action);
+		Assert(IsA(deleteact,DeleteStmt));
+		
+		deleteact->relation = stmt->relation;
+		deleteact->usingClause = stmt->source;
+		deleteact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)deleteact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_DELETE || actqry->utilityStmt != NULL)
+			elog(ERROR, "improper DELETE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{
+		/*an update action*/
+		MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+
+		/*the "targetlist" of the updateact is filled in the parser */
+		updateact->relation = stmt->relation;
+		updateact->fromClause = stmt->source;
+		updateact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)updateact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_UPDATE|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper UPDATE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{
+		/*an insert action*/
+		Node *qual;
+		MergeInsert *insertact;
+
+		insertact = (MergeInsert *)(condact->action);
+		
+		
+		/*the "cols" and "selectStmt" of the insertact is filled in the parser */
+		insertact->relation = stmt->relation;
+
+		/*
+		the merge insert action has a strange feature. 
+		In an ordinary INSERT, the VALUES list can only contains constants and DEFAULT. (am I right??)
+		But in the INSERT action of MERGE command, the VALUES list can have expressions with variables(attributes of the targe and source tables).
+		Besides, in the ordinary INSERT, a VALUES list can never be followed by a WHERE clause. But in MERGE INSERT action, there are matching conditions. 
+
+		Thus, the output qry of this function is an INSERT query in the style of "INSERT...VALUES...", except that we have other range tables and a WHERE clause.
+		Note that it is also different from the "INSERT ... SELECT..." query, in which the whole SELECT is a subquery. (We don't have subquery here).
+		We construct this novel query structure in order to keep consitency with other merge action types (DELETE, UPDATE). 
+		In this way, all the merge action queries are in fact share the very same Range Table, They only differs in their target lists and join trees
+		
+		*/
+		
+				
+		/*parse the action query, this will call transformInsertStmt() which analyzes the VALUES list.*/
+		actqry = transformStmt(pstate, (Node *)insertact);
+
+		/*do the WHERE clause here, Since the transformInsertStmt() function only analyzes the VALUES list but not the WHERE clause*/
+		qual = transformWhereClause(pstate,(Node *)act_qual_expr, "WHERE");
+		
+		actqry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+		
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_INSERT|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper INSERT action in merge stmt");
+
+
+		return actqry;
+	}
+	else
+		elog(ERROR, "unknown action type in MERGE");
+
+	/*never comes here*/
+	return NULL;
+}
+
+
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+	Query	   *qry; 
+
+	ColumnRef *starRef;
+	ResTarget *starResTarget;
+	ListCell *act;
+	ListCell *l;
+	JoinExpr *joinexp;
+	int 	rtindex;
+
+	/*this will never happen, since the garm.y is restricted that only one rel name is allowed to appear in the source table position. 
+	However, if we extent the command in future, we may need to note this check here. 
+	*/
+	if(list_length(stmt->source) != 1) 
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("now we only accept merge command with only ONE source table")));
+		
+	/*now, do the real tranformation of the merge command. */
+	qry = makeNode(Query);
+	qry->commandType = CMD_MERGE;
+
+	/*
+	What we are doing here is to create a query like 
+		"SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+	Note:	
+	1. we set the "match condition" as the join qualification. 
+	The left join will scan both the matched and non-matched tuples.
+
+	2. a normal SELECT query has no "target relation". 
+	But here we need to set the targe relation in query, like the UPDATE/DELETE/INSERT queries. 
+	So this is a left join SELECT with a "target table" in its range table. 
+
+	3. We don't have a specific ACL level for Merge, here we just use ACL_SELECT. 
+	But we will add other ACL levels when handle each merge actions.  
+	*/
+
+
+	/*before analyze the FROM clause, we need to set the target table. 
+	We cannot call setTargetTable() function directly. 
+	We only need the lock target relation, without adding it to Range table. 
+	*/
+	
+	
+	setTargetTableLock(pstate, stmt->relation);
+	
+	
+	
+	/*create the FROM clause. Make the join expression first*/
+	joinexp = makeNode(JoinExpr);
+	joinexp->jointype = JOIN_LEFT;
+	joinexp->isNatural = FALSE;
+	joinexp->larg = linitial(stmt->source);/*source list has only one element*/
+	joinexp->rarg = (Node *)stmt->relation;
+	joinexp->quals = stmt->matchCondition; /*match condtion*/
+
+	/*transform the FROM clause. The target relation and source relation will be add to Rtable here.	*/
+	transformFromClause(pstate, list_make1(joinexp));
+
+	/*the targetList of the main query is "*"	  
+	*/
+	starRef = makeNode(ColumnRef);	
+	starRef->fields = list_make1(makeNode(A_Star));					
+	starRef->location = 1;					
+
+	starResTarget = makeNode(ResTarget);					
+	starResTarget->name = NULL;					
+	starResTarget->indirection = NIL;					
+	starResTarget->val = (Node *)starRef;					
+	starResTarget->location = 1;
+	
+	qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+	/*we don't need the WHERE clause here. Set it null. */
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	/*now , we find out the RTE for the target relation, and do some unfinished jobs*/
+	rtindex = 1;
+	foreach(l, pstate->p_rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+		if(rte->relid == pstate->p_target_relation->rd_id) /*find the RTE*/
+		{
+			pstate->p_target_rangetblentry = rte;
+			rte->requiredPerms = ACL_SELECT;	
+			qry->resultRelation = rtindex;
+			break;
+		}
+		rtindex++;
+	}
+
+	if(pstate->p_target_rangetblentry == NULL)
+		elog(ERROR, "cannot find the RTE for target table");
+	
+
+	qry->rtable = pstate->p_rtable;
+
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	/*
+	 * Top-level aggregates are simply disallowed in MERGE
+	 */
+	if (pstate->p_hasAggs)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("cannot use aggregate function in top level of MERGE"),
+				 parser_errposition(pstate,
+									locate_agg_of_level((Node *) qry, 0))));
+	if (pstate->p_hasWindowFuncs)
+		ereport(ERROR,
+				(errcode(ERRCODE_WINDOWING_ERROR),
+				 errmsg("cannot use window function in MERGE"),
+				 parser_errposition(pstate,
+									locate_windowfunc((Node *) qry))));
+
+
+
+
+	/*
+		the main query is done.
+	then for each actions ,we transform it to a seperate query.
+	the action queries shares the exactly same range table with the main query. 
+	in other words, in the extra condtions of the sub actions, we don't allow involvement of new tables
+	*/
+	
+	qry->mergeActQry = NIL;
+
+	foreach(act,stmt->actions)
+	{
+		MergeConditionAction *mca = lfirst(act);
+		Query *actqry;
+
+		switch(mca->action->type)
+		{
+			case T_MergeDelete:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+				break;
+			case T_MergeUpdate:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+				break;
+			case T_MergeInsert:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;				
+				break;
+			default:
+				elog(ERROR, "unknown MERGE action type %d", mca->type);
+				break;
+
+		}
+
+
+		/*transform the act (and its condition) as a single query. Link it to the top-level query*/
+		actqry = transformMergeActions(pstate, stmt, mca);
+
+		/*since we don't invoke setTargetTable() in transformMergeActions(), we need to set actqry->resultRelation here
+		*/
+		actqry->resultRelation = qry->resultRelation;
+
+
+		qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+	}
+
+
+	/*for a single-action merge, we just stransform it into a orignial update/delete command.
+	but the insert action cannot take this shortcut.
+	*/
+	/*
+	if(list_length(stmt->actions) == 1)
+	{
+		Query *q = linitial(qry->mergeActQry);
+		if(q->commandType == CMD_DELETE || q->commandType == CMD_UPDATE)
+			return q;
+	}
+	*/
+	return qry;
+
+	
+}
+
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..c139bd7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,12 @@ static TypeName *TableFuncTypeName(List *columns);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 
+%type <node>	MergeStmt  opt_and_condition  merge_condition_action   merge_action
+%type <boolean> opt_not	
+%type <list> 	merge_condition_action_list
+
+
+
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
 
@@ -233,7 +239,8 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <boolean>	opt_force opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
-
+				
+				
 %type <list>	OptRoleList AlterOptRoleList
 %type <defelt>	CreateOptRoleElem AlterOptRoleElem
 
@@ -504,6 +511,8 @@ static TypeName *TableFuncTypeName(List *columns);
 
 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
+	MATCHED MERGE
+	
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -725,6 +734,7 @@ stmt :
 			| ListenStmt
 			| LoadStmt
 			| LockStmt
+			| MergeStmt 
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -7297,6 +7307,101 @@ set_target_list:
 /*****************************************************************************
  *
  *		QUERY:
+ *				MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt: 
+			MERGE INTO relation_expr_opt_alias
+			USING  table_ref
+			ON a_expr
+			merge_condition_action_list
+				{
+					MergeStmt *m = makeNode(MergeStmt);
+
+					m->relation = $3;
+					m->source = list_make1($5);  /*although we have only one USING table, but we still make it a list, maybe in future we will allow mutliple USING tables.*/
+					m->matchCondition = $7;
+					m->actions = $8;
+
+					$$ = (Node *)m;
+				}
+				;
+	
+merge_condition_action_list: 
+							merge_condition_action 		
+								{ $$ = list_make1($1); }
+							| merge_condition_action_list merge_condition_action   
+								{ $$ = lappend($1,$2); }
+							;	
+
+merge_condition_action: 	
+							WHEN opt_not MATCHED opt_and_condition THEN merge_action
+							{
+								MergeConditionAction *m = makeNode(MergeConditionAction);
+
+								m->match = $2;
+								m->condition = $4;
+								m->action = $6;
+
+								$$ = (Node *)m;
+							}
+							;
+
+
+opt_and_condition:	
+					AND a_expr 		{$$ = $2;}
+					| /*EMPTY*/ 		{$$ = NULL;}
+					;
+
+opt_not:	
+			NOT			{$$ = false;}
+			| /*EMPTY*/	{$$ = true;}
+			;
+
+merge_action: 	
+				DELETE_P	
+					{$$ = (Node *)makeNode(MergeDelete);}
+				| UPDATE SET set_clause_list 
+					{
+						UpdateStmt *n = makeNode(MergeUpdate);
+						n->targetList = $3;
+						$$ = (Node *)n;
+					}
+				| INSERT values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = $2;
+
+						$$ = (Node *)n;
+					}
+					
+				| INSERT '(' insert_column_list ')' values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = $3;
+						n->selectStmt = $5;
+
+						$$ = (Node *)n;
+					}
+				| INSERT DEFAULT VALUES
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = NULL;
+
+						$$ = (Node *)n; 
+					}
+					
+				;	
+				
+
+
+/*****************************************************************************
+ *
+ *		QUERY:
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
@@ -10935,7 +11040,9 @@ unreserved_keyword:
 			| LOGIN_P
 			| MAPPING
 			| MATCH
+			| MATCHED
 			| MAXVALUE
+			| MERGE
 			| MINUTE_P
 			| MINVALUE
 			| MODE
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index f30132a..657dd9c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -213,6 +213,29 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	return rtindex;
 }
 
+
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+	
+	/* Close old target; this could only happen for multi-action rules */
+	if (pstate->p_target_relation != NULL)
+		heap_close(pstate->p_target_relation, NoLock);
+
+	/*
+	 * Open target rel and grab suitable lock (which we will hold till end of
+	 * transaction).
+	 *
+	 * free_parsestate() will eventually do the corresponding heap_close(),
+	 * but *not* release the lock.
+	 */
+	pstate->p_target_relation = parserOpenTable(pstate, relation,
+												RowExclusiveLock);
+
+	
+}
+
+
 /*
  * Simplify InhOption (yes/no/default) into boolean yes/no.
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..287fa1f 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1837,6 +1837,45 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 }
 
 
+#define insert_rewrite (1<<0) /*insert merge action has already been processed by rewriter*/
+#define delete_rewrite (1<<1) 
+#define update_rewrite (1<<2)
+
+#define	insert_instead (1<<3) /*insert merge action is fully replace by rules.*/
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+
+#define merge_action_already_rewrite(acttype, flag) \
+	((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+		(acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+		(acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+
+#define set_action_rewrite(acttype, flag)	\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_rewrite;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_rewrite;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_rewrite;}
+
+
+				
+#define merge_action_instead(acttype, flag)		\
+			((acttype == CMD_INSERT && (flag & insert_instead)) || \
+				(acttype == CMD_UPDATE && (flag & update_instead)) || \
+				(acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_instead;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_instead;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_instead;}
+
+
 /*
  * QueryRewrite -
  *	  Primary entry point to the query rewriter.
@@ -1861,7 +1900,133 @@ QueryRewrite(Query *parsetree)
 	 *
 	 * Apply all non-SELECT rules possibly getting 0 or many queries
 	 */
-	querylist = RewriteQuery(parsetree, NIL);
+
+	
+	if(parsetree->commandType == CMD_MERGE)
+	{
+		/*for merge query, we have a set of lower-level action queries (not subquery).
+		//each of these action queries should be applied to RewriteQuery(). 
+		*/
+		ListCell   *cell,
+				   *prev,
+				   *next;
+
+		int flag = 0;
+
+		
+		List *pre_qry = NIL;
+		List *post_qry = NIL;
+
+
+		querylist = NIL;
+		
+
+		/*rewrite the merge action queries one by one.*/
+		prev = NULL;
+
+		for (cell = list_head(parsetree->mergeActQry); cell; cell = next)
+		{
+			List *queryList4action = NIL;
+			Query *actionqry;
+			Query *q;
+
+			
+			actionqry = lfirst(cell);
+
+			next = lnext(cell);
+
+			/*if this kind of actions are fully replaced by rules, we delete it from the action list*/	
+			if(merge_action_instead(actionqry->commandType, flag))
+			{
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+
+			/*if this kind of actions are already processed by rewriter, skip it.*/
+			if(merge_action_already_rewrite(actionqry->commandType, flag))
+			{
+
+			
+				prev = cell; 
+				continue;
+			}
+
+			/*ok this action has not been processed before, let's do it now.*/
+			
+			queryList4action = RewriteQuery(actionqry, NIL);
+			set_action_rewrite(actionqry->commandType,flag); /*this kind of actions has been processed.*/
+
+			/*if the returning list is nil, this merge action is replaced by a do-nothing rule*/
+			if(queryList4action == NIL) 
+			{
+				/*set the flag for other merge actions of the same type*/
+				set_action_instead(actionqry->commandType, flag);
+				/*delete the action.*/
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+			
+			/*the merge action query could be one of the elements in the rewriten list. 
+			//if it is in the list, it must be the head or tail.
+			*/
+			q = (Query *)linitial(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the head, the remaining part of the list are the queries generated by rules
+				//we put them in the post_qry list. 
+				*/
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+
+
+				queryList4action = list_delete_first(queryList4action);
+				post_qry = list_concat(post_qry,queryList4action);
+				prev = cell; 
+				continue;
+
+			}
+			
+			
+			q = (Query *)llast(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the tail. Put the rule queries in pre_qry list*/
+
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+					
+				queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+				pre_qry = list_concat(pre_qry,queryList4action);
+
+				prev = cell; 
+				continue;
+
+			}	
+				
+
+			/*here, the merge action query is not in the rewriten query list, which means the action should be deleted
+			//It is replaced by INSTEAD rule(s). We need to delete the action  
+			*/
+			post_qry = list_concat(post_qry,queryList4action);	
+			set_action_instead(actionqry->commandType, flag);
+			parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+		}				
+
+		
+
+		/*finally, put the 3 lists into one. 
+		If all the merge actions are replaced by rules, the original merge query 
+		will not be involved in the querylist.
+		*/
+		querylist = list_concat(pre_qry,querylist);
+		querylist = list_concat(querylist, post_qry);
+			
+	}
+	else
+		querylist = RewriteQuery(parsetree, NIL);
 
 	/*
 	 * Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
 				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
 						 "DELETE %u", queryDesc->estate->es_processed);
 				break;
+			case CMD_MERGE:
+				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+						 "MERGE %u", queryDesc->estate->es_processed);
+				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8960246..2733e5d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
 			case CMD_UPDATE:
 			case CMD_INSERT:
 			case CMD_DELETE:
+			case CMD_MERGE:
 				return false;
 			default:
 				elog(WARNING, "unrecognized commandType: %d",
@@ -1398,6 +1399,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_MergeStmt:
+			tag = "MERGE";
+			break;
+		
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2235,6 +2240,7 @@ GetCommandLogLevel(Node *parsetree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
+		case T_MergeStmt:
 			lev = LOGSTMT_MOD;
 			break;
 
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..7ae2f0a 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,9 @@
 #include "nodes/execnodes.h"
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+
+extern PlanState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
+
 extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 09fdb5d..6c0ae0c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1031,6 +1031,7 @@ typedef struct ModifyTableState
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
+	List 		*mergeActPstates; /*the list of the planstate of each meger command action. NIL if this is not a merge command, all actions' */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..15d11a0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
 	T_Plan = 100,
 	T_Result,
 	T_ModifyTable,
+	T_MergeAction,
 	T_Append,
 	T_RecursiveUnion,
 	T_BitmapAnd,
@@ -261,6 +262,15 @@ typedef enum NodeTag
 	T_DeleteStmt,
 	T_UpdateStmt,
 	T_SelectStmt,
+
+	
+	T_MergeStmt,
+	T_MergeConditionAction,
+	T_MergeUpdate, 
+	T_MergeDelete,  
+	T_MergeInsert,
+
+	
 	T_AlterTableStmt,
 	T_AlterTableCmd,
 	T_AlterDomainStmt,
@@ -511,6 +521,7 @@ typedef enum CmdType
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
 	CMD_DELETE,
+	CMD_MERGE,						/*merge stmt*/
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b591073..d6de83c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -146,6 +146,11 @@ typedef struct Query
 
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
+
+	bool		 isMergeAction; /*if this query is a merge action. */	 
+
+	List 		*mergeActQry; /* the list of all the merge actions. 
+								* used only for merge query statment*/							 
 } Query;
 
 
@@ -990,6 +995,30 @@ typedef struct SelectStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
+/*ZBX: the structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+	NodeTag		type;
+	RangeVar   	*relation;		/*targe relation for merge */
+	List		*source;		/* source relations for the merge. Currently, we only allwo single-source merge, so the length of this list should always be 1 */
+	Node	   	*matchCondition;	/* qualifications of the merge*/
+	List	   	*actions;		/*list  of MergeConditionAction structure. It stores all the match / non-matching conditions and the corresponding actions*/
+
+}MergeStmt;
+
+/*the structure for the actions of MERGE command. Holds info of the clauses like "... WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+	NodeTag		type;
+	bool 		match; /*match or not match*/
+	Node		*condition;/*the AND condition for this action*/
+	Node 		*action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
 
 /* ----------------------
  *		Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..5c1bc5f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,8 +169,16 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List 		*mergeActPlan;	/*the plans for merge actions, which are also ModifyTable nodes*/
 } ModifyTable;
 
+
+typedef struct MergeAction
+{
+	Plan plan;
+	CmdType	operation;/* INSERT, UPDATE, or DELETE */
+}MergeAction;
+
 /* ----------------
  *	 Append node -
  *		Generate the concatenation of the results of sub-plans.
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 805dee7..6d7ff4f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -34,6 +34,7 @@ extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
 				 bool hasRecursion, double tuple_fraction,
 				 PlannerInfo **subroot);
+extern ModifyTable *merge_action_planner(PlannerGlobal *glob, Query *parse, Plan *top_plan);
 
 extern Expr *expression_planner(Expr *expr);
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..436d459 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -229,7 +229,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..e4312d8 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,8 @@
 extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
+
 extern bool interpretInhOption(InhOption inhOpt);
 extern bool interpretOidsOption(List *defList);
 
#6Robert Haas
robertmhaas@gmail.com
In reply to: Boxuan Zhai (#5)
Re: merge command - GSoC progress

On Wed, Jul 28, 2010 at 11:51 AM, Boxuan Zhai <bxzhai2010@gmail.com> wrote:

I have fixed the action qual problem. Now the system can run merge command,
with quals.

I create a clean patch file (no debug clauses). See the attachment.

Please try this new command if you have interest.

So, I tried this today, but:

- I got some compiler warnings in analyze.c, and
- when tried to run 'make check' with the patch applied, initdb failed.

So you still need to do some more bug-squashing on this...

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#7Greg Smith
greg@2ndquadrant.com
In reply to: Boxuan Zhai (#5)
Re: merge command - GSoC progress

Boxuan Zhai wrote:

I create a clean patch file (no debug clauses). See the attachment.

Some general coding feedback for you on this.

It's not obvious to people who might want to try this out what exactly
they are supposed to do. Particularly for complicated patches like
this, where only a subset of the final feature might actually be
implemented, some sort of reviewer guide suggesting what should and
shouldn't work would be extremely helpful. I recall there was some sort
of patch design guide in an earlier version of this patch; it doesn't
seem to be there anymore. Don't remember if that had any examples in it.

Ultimately, the examples of working behavior for this patch will need to
be put into code. The way that happens is by adding working examples
into the regression tests for the database. If you had those done for
this patch, I wouldn't have to ask for code examples; I could just look
at the source to the regression output and see how to use it against the
standard database the regression samples create, and then execute
against. This at least lets you avoid having to generate some test
data, because there will already be some in the regression database for
you to use. There is an intro this topic at
http://wiki.postgresql.org/wiki/Regression_test_authoring Another
common way to generate test data is to run pgbench which creates 4
tables and populates them with data.

As far as the patch itself goes, you have some work left on cleaning
that up still you'll need to do eventually. What I would suggest is
actually reading the patch itself; don't just generate it and send it,
read through the whole thing like someone new to it would do. One way
you can improve what you've done already is to find places where you
have introduced changes to the code structure just through editing.
Here's an example of what I'm talking about, from line 499 of your patch:

-            break;
+            break;       

This happened because you added two invisible tabs to the end of this
line. This makes the patch larger for no good reason and tends to
infuriate people who work on the code. There's quite a bit of extra
lines added in here too that should go. You should consider reducing
the ultimate size of the patch in terms of lines a worthwhile use of
your time, even if it doesn't change how things work. There's lots of
examples in this one where you put two or three lines between two
statements when a single one would match the look of the code in that
file. A round of reading the diff looking for that sort of problem
would be useful.

Another thing you should do is limit how long each line is when
practical. You have lots of seriously wide comment lines here right now
in particular. While there are some longer lines in the PostgreSQL code
right now, that's code, not comments. And when you have a long line and
a long comment, don't tack the comment onto the end. Put it on the line
above instead. Also, don't use "//" in the middle of comments the way
you've done in a few places here.

Getting some examples sorted out and starting on regression tests is
more important than the coding style parts I think, just wanted to pass
those along while I noticed them reading the patch, so you could start
looking out for them more as you continue to work on it.

--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us

#8Robert Haas
robertmhaas@gmail.com
In reply to: Greg Smith (#7)
Re: merge command - GSoC progress

On Fri, Jul 30, 2010 at 12:21 PM, Greg Smith <greg@2ndquadrant.com> wrote:

 If you had those done for this patch, I
wouldn't have to ask for code examples; I could just look at the source to
the regression output and see how to use it against the standard database
the regression samples create, and then execute against.

I agree. While not every feature needs regression tests, something
this complex certainly does. Also, running the regression tests
(frequently!) can help you realize when you've broken the existing
code before too much time goes by and it's no longer easy to figure
out which change is responsible for the breakage.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company

#9Andres Freund
andres@anarazel.de
In reply to: Greg Smith (#7)
Re: merge command - GSoC progress

On Friday 30 July 2010 18:21:49 Greg Smith wrote:

-            break;
+            break;       

This happened because you added two invisible tabs to the end of this
line. This makes the patch larger for no good reason and tends to

If you want to remove changes like this using:
"git checkout -p HEAD"
is very useful if youre using git. It allows you to selectively revert hunks
of not-checked-in changes...

Andres

#10Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Greg Smith (#7)
Re: merge command - GSoC progress

2010/7/31 Greg Smith <greg@2ndquadrant.com>

Boxuan Zhai wrote:

I create a clean patch file (no debug clauses). See the attachment.

Some general coding feedback for you on this.

Thanks for your consideration!

It's not obvious to people who might want to try this out what exactly they
are supposed to do. Particularly for complicated patches like this, where
only a subset of the final feature might actually be implemented, some sort
of reviewer guide suggesting what should and shouldn't work would be
extremely helpful. I recall there was some sort of patch design guide in an
earlier version of this patch; it doesn't seem to be there anymore. Don't
remember if that had any examples in it.

I am now working on fixing a bug which makes the system unable to initdb. I

will update my page in postgres Wiki with a detailed instruction of my
implementation and testing examples soon, with my next patch file.

Ultimately, the examples of working behavior for this patch will need to be
put into code. The way that happens is by adding working examples into the
regression tests for the database. If you had those done for this patch, I
wouldn't have to ask for code examples; I could just look at the source to
the regression output and see how to use it against the standard database
the regression samples create, and then execute against. This at least lets
you avoid having to generate some test data, because there will already be
some in the regression database for you to use. There is an intro this
topic at http://wiki.postgresql.org/wiki/Regression_test_authoring Another common way to generate test data is to run pgbench which creates 4
tables and populates them with data.

I will try to add my testing examples to the gregression folder.

As far as the patch itself goes, you have some work left on cleaning that
up still you'll need to do eventually. What I would suggest is actually
reading the patch itself; don't just generate it and send it, read through
the whole thing like someone new to it would do. One way you can improve
what you've done already is to find places where you have introduced changes
to the code structure just through editing. Here's an example of what I'm
talking about, from line 499 of your patch:

- break;
+ break;
This happened because you added two invisible tabs to the end of this line.
This makes the patch larger for no good reason and tends to infuriate
people who work on the code. There's quite a bit of extra lines added in
here too that should go. You should consider reducing the ultimate size of
the patch in terms of lines a worthwhile use of your time, even if it
doesn't change how things work. There's lots of examples in this one where
you put two or three lines between two statements when a single one would
match the look of the code in that file. A round of reading the diff
looking for that sort of problem would be useful.

Another thing you should do is limit how long each line is when practical.
You have lots of seriously wide comment lines here right now in particular.
While there are some longer lines in the PostgreSQL code right now, that's
code, not comments. And when you have a long line and a long comment, don't
tack the comment onto the end. Put it on the line above instead. Also,
don't use "//" in the middle of comments the way you've done in a few places
here.

Sorry for these mistakes, again. I promise that the same thing will not

happen in my next patch.

Show quoted text

Getting some examples sorted out and starting on regression tests is more
important than the coding style parts I think, just wanted to pass those
along while I noticed them reading the patch, so you could start looking out
for them more as you continue to work on it.

--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us <http://www.2ndquadrant.us/&gt;

#11Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Boxuan Zhai (#10)
1 attachment(s)
Re: merge command - GSoC progress

Dear Robert,

I have finished the tested edition of my code.

I think there are no redundant lines in this time's patch file.

And, I have tested the running of MERGE command with different situations. I
am sorry that I didn't create regression test files, because I am not sure
how to add new files in the git package. But, I have written web pages in
Postgres Wiki. I explain the details of my implementation and a set of
testing examples.

Please refer to the following pages if you are interested.
https://wiki.postgresql.org/wiki/Add_MERGE_command_GSoC_2010

https://wiki.postgresql.org/wiki/Implementation_detalis
https://wiki.postgresql.org/wiki/Test_examples

Thanks!

Yours Boxuan

Attachments:

tested_merge.tarapplication/x-tar; name=tested_merge.tarDownload
tested_merge.patch0000644000076400010400000017043311426077071015631 0ustar  bxzhaiAdministratorsdiff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..cdb492b 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -73,6 +73,8 @@ static void show_sort_keys(SortState *sortstate, List *ancestors,
 static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
 static const char *explain_get_index_name(Oid indexId);
+static void ExplainMergeActions(ModifyTableState *mt_planstate, 
+									List *ancestors, ExplainState *es);
 static void ExplainScanTarget(Scan *plan, ExplainState *es);
 static void ExplainMemberNodes(List *plans, PlanState **planstates,
 				   List *ancestors, ExplainState *es);
@@ -636,6 +638,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				case CMD_DELETE:
 					pname = operation = "Delete";
 					break;
+				case CMD_MERGE:
+					pname = operation = "Merge";
+					break;
 				default:
 					pname = "???";
 					break;
@@ -1190,6 +1195,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	switch (nodeTag(plan))
 	{
 		case T_ModifyTable:
+			ExplainMergeActions((ModifyTableState *)planstate, ancestors, es);
+			
 			ExplainMemberNodes(((ModifyTable *) plan)->plans,
 							   ((ModifyTableState *) planstate)->mt_plans,
 							   ancestors, es);
@@ -1482,6 +1489,55 @@ explain_get_index_name(Oid indexId)
 	return result;
 }
 
+static void
+ExplainMergeActions(ModifyTableState *mt_planstate, List *ancestors, ExplainState *es)
+{
+	ListCell *l;
+	
+	if(mt_planstate->operation != CMD_MERGE || mt_planstate->mergeActPstates == NIL)
+		return;
+
+	foreach(l,mt_planstate->mergeActPstates)
+	{
+		ModifyTableState *mt_state = (ModifyTableState *)lfirst(l);
+
+		MergeActionState *act_pstate = (MergeActionState *)mt_state->mt_plans[0];
+
+		MergeAction *act_plan = (MergeAction *)act_pstate->ps.plan;
+
+		switch(act_pstate->operation)
+		{
+			case CMD_INSERT:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Insert When Not Matched And",es);
+				else
+					ExplainPropertyText("Action", "Insert When Not Matched",es);				
+				break;
+			case CMD_UPDATE:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Update When Matched And",es);
+				else
+					ExplainPropertyText("Action", "Update When Matched",es);	
+				break;
+			case CMD_DELETE:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Delete When Matched And",es);
+				else
+					ExplainPropertyText("Action", "Delete When Matched",es);	
+				break;	
+			default:
+				elog(ERROR, "unknown merge action type");
+		}
+
+		/*
+		*	We need a faltened qual expression for show_qual
+		*/
+		show_qual(act_plan->flattenedqual, "  qual", &act_pstate->ps, ancestors, true, es);
+	}
+	
+}
+
+
 /*
  * Show the target of a Scan node
  */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2f33fdb..56c13ee 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -171,6 +171,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_INSERT:
 		case CMD_DELETE:
 		case CMD_UPDATE:
+		case CMD_MERGE:	
 			estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_MergeAction:
+			result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+													   estate, eflags);
+			break;
+			
 		case T_Append:
 			result = (PlanState *) ExecInitAppend((Append *) node,
 												  estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..bd84784 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -583,6 +583,116 @@ lreplace:;
 }
 
 
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   ModifyTableState *node,
+		   EState *estate)
+{
+
+	TupleTableSlot *actslot = NULL;
+	TupleTableSlot *res = NULL;
+	ListCell *each;
+
+	/*
+	*	try the merge actions one by one
+	*/
+	foreach(each, node->mergeActPstates)
+	{
+		ModifyTableState *mt_pstate;
+
+		MergeActionState *action_pstate; 
+
+		ExprContext *econtext;
+
+		
+		mt_pstate = (ModifyTableState *)lfirst(each);
+
+		/*
+		*	mt_pstate is supposed to have only ONE mt_plans, 
+		*	which is a MergeActionState
+		*/
+		Assert(mt_pstate->mt_nplans == 1);
+
+		action_pstate = (MergeActionState *)mt_pstate->mt_plans[0];
+
+		/*
+		*	If tupleid == NULL, it is a NOT MATCHED case, in which we can only do INSERT action
+		*	If tupleid != NULL, it is a MATCHED case, in which we can only do UPDATE or DELETE action
+		*/		
+		if((tupleid == NULL && action_pstate->operation != CMD_INSERT) ||
+			(tupleid != NULL && action_pstate->operation == CMD_INSERT))
+		{
+			continue;
+		}
+
+		/*
+		*	Setup the expression context
+		*/
+		econtext = action_pstate->ps.ps_ExprContext;
+
+		/*
+		If the action has an additional qual, which is not satisfied, skip it
+		*/	
+		
+		if(action_pstate->ps.qual) 
+		{	
+			ResetExprContext(econtext);
+		
+			econtext->ecxt_scantuple = slot;
+			econtext->ecxt_outertuple = planSlot;
+
+			if(!ExecQual(action_pstate->ps.qual, econtext,false))
+			{	
+				continue;
+			}
+		}
+		/*
+		*	Now we start to exec this action. Firstly , project the result tuple slot. 
+		*	The merge action result slot is projected in the same way as a returning slot. 
+		*	so we call ExecProcessReturning() directly here.
+		*/
+		if(action_pstate->operation != CMD_DELETE)
+			actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo, slot, planSlot);
+
+		switch (action_pstate->operation)
+		{
+			case CMD_INSERT:
+				res = ExecInsert(actslot, planSlot, estate);
+				break;
+			case CMD_UPDATE:
+				res = ExecUpdate(tupleid,
+							actslot,
+							planSlot,
+							&mt_pstate->mt_epqstate,
+							estate);
+				break;
+			case CMD_DELETE:
+				res = ExecDelete(tupleid, 
+							planSlot, 
+							&mt_pstate->mt_epqstate,
+							estate);
+				break;
+			default:
+				elog(ERROR, "unknown merge action type");
+				break;
+		}
+
+
+		/*
+		* The current action is executed, no need to do the remain actions
+		*/	
+		return res;
+	}
+
+	/*
+	* Here, no action is taken. Let's pass this slot
+	*/	
+	return NULL;
+	
+}
+
 /*
  * Process BEFORE EACH STATEMENT triggers
  */
@@ -603,6 +713,9 @@ fireBSTriggers(ModifyTableState *node)
 			ExecBSDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the BS triggers of merge\n");
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -629,6 +742,9 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the AS triggers of merge\n");
+			break;	
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -708,20 +824,34 @@ ExecModifyTable(ModifyTableState *node)
 			/*
 			 * extract the 'ctid' junk attribute.
 			 */
-			if (operation == CMD_UPDATE || operation == CMD_DELETE)
+			if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 			{
 				Datum		datum;
 				bool		isNull;
 
 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
 											 &isNull);
-				/* shouldn't ever get a null result... */
+				
 				if (isNull)
-					elog(ERROR, "ctid is NULL");
-
-				tupleid = (ItemPointer) DatumGetPointer(datum);
-				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
-				tupleid = &tuple_ctid;
+				{
+					/*
+					*	shouldn't ever get a null result for update and delete. 
+					*	Merge command will get a null ctid in "NOT MATCHED" case
+					*/
+					if(operation != CMD_MERGE)
+						elog(ERROR, "ctid is NULL");
+					else
+						tupleid = NULL;
+				}	
+				else
+				{
+				
+					tupleid = (ItemPointer) DatumGetPointer(datum);
+					
+					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
+					tupleid = &tuple_ctid;
+					
+				}
 			}
 
 			/*
@@ -744,6 +874,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecDelete(tupleid, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
+			case CMD_MERGE:
+				slot = ExecMerge(tupleid, slot, planSlot,
+								  node, estate);
+				break;	
+				
 			default:
 				elog(ERROR, "unknown operation");
 				break;
@@ -771,6 +906,86 @@ ExecModifyTable(ModifyTableState *node)
 	return NULL;
 }
 
+
+/*
+*	When init a merge plan, we also need init its action plans. 
+*	These action plans are "MergeAction" plans (new plan node for the merge actions).
+*	
+*	In this function, we only want to handle the tlist and qual in the plan.
+*	The returning result is a  "MergeActionState".  
+*/
+MergeActionState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+	MergeActionState *result;
+	
+	/*
+	 * do nothing when we get to the end of a leaf on tree.
+	 */
+	if (node == NULL)
+		return NULL;
+
+		
+	/*
+	 * create state structure
+	 */
+	result = makeNode(MergeActionState);
+	result->operation = node->operation;
+	result->ps.plan = (Plan *)node;
+	result->ps.state = estate;
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &result->ps);
+
+
+	
+	/*
+	 * initialize tuple type 
+	 */
+	ExecAssignResultTypeFromTL(&result->ps);
+
+		
+	/*
+	 * create expression context for node
+	 */
+	 
+	ExecAssignExprContext(estate, &result->ps);
+
+
+	/*
+	 * initialize child expressions
+	 */
+	result->ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->plan.targetlist,  &result->ps);
+
+	
+	result->ps.qual = (List *)
+		ExecInitExpr((Expr *) node->plan.qual, &result->ps);
+
+	
+	/*
+	* init the projection information
+	*/
+	ExecAssignProjectionInfo(&result->ps, NULL);
+
+	/*
+	do we need a check for the plan output here ?
+	
+	if(estate->es_result_relation_info == NULL)
+		printf("re infro is null\n");
+
+	if(estate->es_result_relation_info->ri_RelationDesc == NULL)
+		printf("r relation desc is nUll\n");
+		ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+								node->targetlist);
+	printf("finish check plan output for merge actio \n");	
+	*/
+	return result;
+}
+
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -786,6 +1001,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	bool 		isMergeAction = false;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -826,6 +1042,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+
+		/*
+		* 	test if this subplan node is a MergeAction. 
+		* 	We need this information for setting the junckfilter. 
+		*	juckfiler is necessary for an ordinary UPDATE/DELETE plan, 
+		*	but not for an UPDATE/DELETE merge action  
+		*/		
+		if(IsA(subplan, MergeAction))
+			isMergeAction = true;
+		
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 		estate->es_result_relation_info++;
 		i++;
@@ -955,7 +1181,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
-				junk_filter_needed = true;
+			case CMD_MERGE:	
+				if(!isMergeAction)
+					junk_filter_needed = true;
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -978,9 +1206,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 							resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
 									   ExecInitExtraTupleSlot(estate));
 
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
+				if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 				{
-					/* For UPDATE/DELETE, find the ctid junk attr now */
+					/* For UPDATE/DELETE/MERGE, find the ctid junk attr now */
 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
 						elog(ERROR, "could not find junk ctid column");
@@ -1006,6 +1234,21 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (estate->es_trig_tuple_slot == NULL)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	
+	/*
+	* for the merge actions, we need to similar things as above
+	*/
+
+	foreach(l, node->mergeActPlan)
+	{
+		PlanState *actpstate = ExecInitNode((Plan *)lfirst(l),  estate, 0);
+		/*
+		* put the pstates of each action into ModifyTableState
+		*/
+		mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+		
+	}
+	
 	return mtstate;
 }
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 93dcef5..13f2153 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(mergeActPlan);
 
 	return newnode;
 }
@@ -2273,6 +2274,10 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 
+	COPY_SCALAR_FIELD(isMergeAction);
+	/*merge action list*/
+	COPY_NODE_FIELD(mergeActQry); 
+
 	return newnode;
 }
 
@@ -2343,6 +2348,64 @@ _copySelectStmt(SelectStmt *from)
 	return newnode;
 }
 
+
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+	MergeStmt *newnode = makeNode(MergeStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(source);
+	COPY_NODE_FIELD(matchCondition);
+	COPY_NODE_FIELD(actions);
+	
+	return newnode;
+	
+}
+
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+	MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+	COPY_SCALAR_FIELD(match);
+	COPY_NODE_FIELD(condition);
+	COPY_NODE_FIELD(action);
+
+	return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+	MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+	newNode->type = T_MergeUpdate;
+
+	return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+	MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+	newNode->type = T_MergeInsert;
+
+	return newNode;
+}
+
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+	MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+	newNode->type = T_MergeDelete;
+
+	return newNode;
+}
+
+
+
 static SetOperationStmt *
 _copySetOperationStmt(SetOperationStmt *from)
 {
@@ -3903,6 +3966,21 @@ copyObject(void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_MergeStmt:
+			retval = _copyMergeStmt(from);
+			break;
+		case T_MergeConditionAction:
+			retval = _copyMergeConditionAction(from);
+			break;
+		case T_MergeUpdate:
+			retval = _copyMergeUpdate(from);
+			break;
+		case T_MergeInsert:
+			retval = _copyMergeInsert(from);
+			break;
+		case T_MergeDelete:
+			retval = _copyMergeDelete(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5d83727..572f6df 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,6 +878,7 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
 
+	COMPARE_SCALAR_FIELD(isMergeAction);
 	return true;
 }
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 79baf4f..339b79f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(mergeActPlan);
 }
 
 static void
@@ -2019,8 +2020,60 @@ _outQuery(StringInfo str, Query *node)
 	WRITE_NODE_FIELD(limitCount);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(setOperations);
+	WRITE_BOOL_FIELD(isMergeAction);
+	WRITE_NODE_FIELD(mergeActQry);
 }
 
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+	WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+	WRITE_BOOL_FIELD(match);
+	
+	WRITE_NODE_FIELD(condition);
+	WRITE_NODE_FIELD(action);
+
+
+}
+
+static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+	WRITE_NODE_TYPE("MERGESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(source);
+	WRITE_NODE_FIELD(matchCondition);
+	WRITE_NODE_FIELD(actions);
+
+}
+
+static void
+_outMergeAction(StringInfo str, MergeAction*node)
+{
+	_outPlanInfo(str, (Plan *)node);
+
+	WRITE_ENUM_FIELD(operation, CmdType);
+
+}
+
+static void 
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+	WRITE_NODE_TYPE("DELETESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(usingClause);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_NODE_FIELD(returningList);
+
+
+}
+
+
+
 static void
 _outSortGroupClause(StringInfo str, SortGroupClause *node)
 {
@@ -2904,6 +2957,19 @@ _outNode(StringInfo str, void *obj)
 			case T_XmlSerialize:
 				_outXmlSerialize(str, obj);
 				break;
+			case T_MergeAction:
+				_outMergeAction(str, obj);
+				break;
+			case T_MergeStmt:
+				_outMergeStmt(str, obj);
+				break;
+			case T_MergeConditionAction:
+				_outMergeConditionAction(str,obj);
+				break;
+			case T_DeleteStmt:
+				_outDeleteStmt(str,obj);
+				break;
+					
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bc6e2a6..b792132 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -218,7 +218,9 @@ _readQuery(void)
 	READ_NODE_FIELD(limitCount);
 	READ_NODE_FIELD(rowMarks);
 	READ_NODE_FIELD(setOperations);
-
+	READ_BOOL_FIELD(isMergeAction);
+	READ_NODE_FIELD(mergeActQry);
+	
 	READ_DONE();
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..0057547 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -565,6 +565,26 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											 returningLists,
 											 rowMarks,
 											 SS_assign_special_param(root));
+
+			/*do a simple plan for each actions in the merge command.
+			*put them in mergeActPlan list;
+			*/
+			if(parse->commandType == CMD_MERGE)
+			{
+				ListCell *l;
+				ModifyTable *top_plan = (ModifyTable *)plan;
+				foreach(l, parse->mergeActQry)
+				{
+					
+					Plan *actplan = (Plan *)merge_action_planner(glob, 
+																		(Query *)lfirst(l),
+																		(Plan *)linitial(top_plan->plans)
+																	);
+
+					top_plan->mergeActPlan = lappend(top_plan->mergeActPlan, actplan);
+				}
+
+			}
 		}
 	}
 
@@ -584,6 +604,109 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	return plan;
 }
 
+
+
+ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+				 Plan *top_plan)
+{
+	PlannerInfo *root;
+	MergeAction	*actplan;
+	ModifyTable *result;
+	
+	List	   	*returningLists;
+	List 		*rowMarks;
+
+	/*
+	 * no having clause in a merge action
+	 */
+	Assert(parse->havingQual == NULL);
+
+
+	/* Create a PlannerInfo data structure for this subquery */
+	root = makeNode(PlannerInfo);
+	root->parse = parse;
+	root->glob = glob;
+	root->query_level = 1;
+	root->parent_root = NULL;
+	root->planner_cxt = CurrentMemoryContext;
+	root->init_plans = NIL;
+	root->cte_plan_ids = NIL;
+	root->eq_classes = NIL;
+	root->append_rel_list = NIL;
+	root->hasPseudoConstantQuals = false;
+	root->hasRecursion = false;
+	root->wt_param_id = -1;
+	root->non_recursive_plan = NULL;
+
+
+	/*
+	*	Create the action plan node
+	*/
+	actplan = makeNode(MergeAction);
+	actplan->operation = parse->commandType;
+	
+	/*
+	 * Do expression preprocessing on targetlist and quals.
+	 */
+	parse->targetList = (List *)
+		preprocess_expression(root, (Node *) parse->targetList,
+							  EXPRKIND_TARGET);
+
+	preprocess_qual_conditions(root, (Node *) parse->jointree);
+	
+
+	/*
+	*	we need a flat qual for explaining
+	*/	
+	actplan->flattenedqual = flatten_join_alias_vars(root, parse->jointree->quals);
+	
+	/*copy the cost from the top_plan*/
+	actplan->plan.startup_cost = top_plan->startup_cost;
+	actplan->plan.total_cost = top_plan->total_cost;
+	actplan->plan.plan_rows = top_plan->plan_rows;
+	actplan->plan.plan_width = top_plan->plan_width;
+	
+	/*
+	*	prepare the result 
+	*/
+	actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+	actplan->plan.qual = (List *)parse->jointree->quals;
+	push_up_merge_action_vars(actplan, parse);
+
+	if (parse->returningList)
+	{
+		List	   *rlist;
+
+		Assert(parse->resultRelation);
+		rlist = set_returning_clause_references(root->glob,
+												parse->returningList,
+												&actplan->plan,
+											  parse->resultRelation);
+		returningLists = list_make1(rlist);
+	}
+	else
+		returningLists = NIL;
+
+
+	if (parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
+	
+	result = make_modifytable(parse->commandType,
+										   copyObject(root->resultRelations),
+											 list_make1(actplan),
+											 returningLists,
+											 rowMarks,
+											 SS_assign_special_param(root));
+
+	
+	return result;
+}
+
+
 /*
  * preprocess_expression
  *		Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..5685d52 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 								  result_relation, range_table);
 
 	/*
-	 * for "update" and "delete" queries, add ctid of the result relation into
+	 * for "update" , "delete"  and "merge" queries, add ctid of the result relation into
 	 * the target list so that the ctid will propagate through execution and
 	 * ExecutePlan() will be able to identify the right tuple to replace or
 	 * delete.	This extra field is marked "junk" so that it is not stored
 	 * back into the tuple.
+	 *  
+	 * BUT, if the query node is a merge action, we don't need to expend the ctid attribute in tlist.
+	 * The tlist of the merge top level plan already contains a "ctid" junk attr of the target relation.
 	 */
-	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+	 
+	if(!parse->isMergeAction  &&  
+			(command_type == CMD_UPDATE || 
+			command_type == CMD_DELETE || 
+			command_type == CMD_MERGE))
 	{
 		TargetEntry *tle;
 		Var		   *var;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92c2208..f1fcbaa 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -67,6 +67,16 @@ typedef struct
 	bool		inserted_sublink;		/* have we inserted a SubLink? */
 } flatten_join_alias_vars_context;
 
+typedef struct
+{
+	int varno_source;
+	int varno_target;
+	int varno_join;
+
+	int offset_source;
+	int offset_target;
+} push_up_merge_action_vars_context;
+
 static bool pull_varnos_walker(Node *node,
 				   pull_varnos_context *context);
 static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
@@ -83,6 +93,8 @@ static bool pull_var_clause_walker(Node *node,
 static Node *flatten_join_alias_vars_mutator(Node *node,
 								flatten_join_alias_vars_context *context);
 static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static bool push_up_merge_action_vars_walker(Node *node, 
+								push_up_merge_action_vars_context *context);
 
 
 /*
@@ -677,6 +689,88 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 								  (void *) context);
 }
 
+/*
+*	When prepare for the MERGE command, we have made a left join between the 
+*	source table and target table as the main plan. In this case, the range table contains 
+*	ONLY THREE range table entries: 
+*	The first one is that of the source table, which may be a subquery or a plain table
+*	The second one is the entry of the targe table, which is a plain table
+*	The third one is a join expression with the sourse table and target table as its parameters.
+*
+*	Each merge action of the command has its own query and plan nodes as well. And, the vars 
+*	in its target list and qual expressions may refers to the attribute in any one of the above 3 
+* 	range table entries.
+*	
+*	However, since the result tuple slots of merge actions are projected from the returned tuple of
+*	the join, we need to mapping the vars of source table and target table to their corresponding 
+*	attributes in the third range table entry. 
+*
+*	This function does the opposit of the flatten_join_alias_vars() function. It walks through the 
+*	target list and qual of a MergeAction plan, changes the vars' varno and varattno to the 
+*	corresponding position in the upper level join RTE.
+*	
+*/
+void 
+push_up_merge_action_vars(MergeAction *actplan, Query *actqry)
+{
+	push_up_merge_action_vars_context context;
+	RangeTblEntry *source_rte = rt_fetch(1,actqry->rtable);
+
+
+	/*
+	* 	We are supposed to do a  more careful assingment of the values in context
+	*	But lets take a shortcut for simple.
+	*/
+	context.varno_source = 1;
+	context.varno_target = 2;
+	context.varno_join = 3;
+
+	context.offset_source = 0;
+
+	
+	context.offset_target = list_length(source_rte->eref->colnames);
+
+	push_up_merge_action_vars_walker(actplan->plan.targetlist, &context);
+	
+	push_up_merge_action_vars_walker(actplan->plan.qual, &context);
+	
+}
+
+
+static bool
+push_up_merge_action_vars_walker(Node *node, 
+									push_up_merge_action_vars_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var *var = (Var *)node;
+
+		if(var->varno == context->varno_source)
+		{
+			var->varno = context->varno_join;
+			var->varattno += context->offset_source;
+			return false;
+		}
+		else if(var->varno == context->varno_target)
+		{
+			var->varno = context->varno_join;
+			var->varattno += context->offset_target;
+			return false;
+		}
+		else if(var->varno == context->varno_join)
+			return false;
+		else
+			elog(ERROR, "the vars in merge action tlist of qual should only belongs to the source table or targe table");
+		
+		
+	}
+	
+	return expression_tree_walker(node, push_up_merge_action_vars_walker,
+								  (void *) context);
+}
+
 
 /*
  * flatten_join_alias_vars
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..58d7a49 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
 static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
 static List *transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos);
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +165,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			 * Optimizable statements
 			 */
 		case T_InsertStmt:
+		case T_MergeInsert:
 			result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
 			break;
 
 		case T_DeleteStmt:
+		case T_MergeDelete:
 			result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
 			break;
 
 		case T_UpdateStmt:
+		case T_MergeUpdate:
 			result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
 			break;
 
+		case T_MergeStmt:
+			result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+			break; 
+
 		case T_SelectStmt:
 			{
 				SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +290,33 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	qry->commandType = CMD_DELETE;
 
-	/* set up range table with just the result rel */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_DELETE);
-
 	qry->distinctClause = NIL;
 
+	if(IsA(stmt,MergeDelete))
+		qry->isMergeAction = true;
 	/*
-	 * The USING clause is non-standard SQL syntax, and is equivalent in
-	 * functionality to the FROM list that can be specified for UPDATE. The
-	 * USING keyword is used rather than FROM because FROM is already a
-	 * keyword in the DELETE syntax.
-	 */
-	transformFromClause(pstate, stmt->usingClause);
+	* The input stmt could be a MergeDelete node. 
+	* In this case, we don't need the process on range table.
+	*/
+	if(IsA(stmt, DeleteStmt))
+	{
+		/* set up range table with just the result rel */
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_DELETE);
 
+		
+
+		/*
+		 * The USING clause is non-standard SQL syntax, and is equivalent in
+		 * functionality to the FROM list that can be specified for UPDATE. The
+		 * USING keyword is used rather than FROM because FROM is already a
+		 * keyword in the DELETE syntax.
+		 */
+		transformFromClause(pstate, stmt->usingClause);
+	}
+	
 	qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -342,11 +361,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->commandType = CMD_INSERT;
 	pstate->p_is_insert = true;
 
+	if(IsA(stmt,MergeInsert))
+		qry->isMergeAction = true;
+	
 	/*
 	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
 	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
 	 * efficiency and so we can handle DEFAULT specifications.
 	 */
+
+	/*a MergeInsert statment is always a VALUE clause*/
 	isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
 	/*
@@ -382,7 +406,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * mentioned in the SELECT part.  Note that the target table is not added
 	 * to the joinlist or namespace.
 	 */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
+	if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/ 
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
 										 false, false, ACL_INSERT);
 
 	/* Validate stmt->cols list, or build default list if no list given */
@@ -1730,16 +1755,23 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_update = true;
 
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_UPDATE);
+	if(IsA(stmt,MergeUpdate))
+		qry->isMergeAction = true;
 
-	/*
-	 * the FROM clause is non-standard SQL syntax. We used to be able to do
-	 * this with REPLACE in POSTQUEL so we keep the feature.
-	 */
-	transformFromClause(pstate, stmt->fromClause);
+	
+	if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+	{
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_UPDATE);
+
+		/*
+		 * the FROM clause is non-standard SQL syntax. We used to be able to do
+		 * this with REPLACE in POSTQUEL so we keep the feature.
+		 */
+		transformFromClause(pstate, stmt->fromClause);
+	}
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -2241,3 +2273,304 @@ applyLockingClause(Query *qry, Index rtindex,
 	rc->pushedDown = pushedDown;
 	qry->rowMarks = lappend(qry->rowMarks, rc);
 }
+
+
+
+/*transform a action of merge command into a query. 
+No change of the pstate range table is allowed in this function. 
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+	Query *actqry = makeNode(Query);
+	A_Expr *match_expr; 
+	A_Expr *act_qual_expr;
+
+	/*
+	*	firstly, we need to make sure that DELETE and UPDATE 
+	*	actions are only taken in MATCHED condition, 
+	*	and INSERTs are only takend when not MATCHED
+	*/
+	if(IsA(condact->action, MergeDelete)) 
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{	
+		if(condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+	}
+	else
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("UNKONWN action type in MERGE")));
+
+
+		
+	act_qual_expr = condact->condition;
+	
+	/*use the transfomStmt() to parse all types of actions*/
+	if(IsA(condact->action, MergeDelete))
+	{
+		/*a delete action*/
+		MergeDelete *deleteact = (MergeDelete *)(condact->action);
+		Assert(IsA(deleteact,DeleteStmt));
+		
+		deleteact->relation = stmt->relation;
+		deleteact->usingClause = stmt->source;
+		deleteact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)deleteact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_DELETE || actqry->utilityStmt != NULL)
+			elog(ERROR, "improper DELETE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{
+		/*an update action*/
+		MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+
+		/*the "targetlist" of the updateact is filled in the parser */
+		updateact->relation = stmt->relation;
+		updateact->fromClause = stmt->source;
+		updateact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)updateact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_UPDATE|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper UPDATE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{
+		/*an insert action*/
+		Node *qual;
+		MergeInsert *insertact;
+
+		insertact = (MergeInsert *)(condact->action);
+		
+		
+		/*the "cols" and "selectStmt" of the insertact is filled in the parser */
+		insertact->relation = stmt->relation;
+
+		/*
+		the merge insert action has a strange feature. 
+		In an ordinary INSERT, the VALUES list can only contains constants and DEFAULT. (am I right??)
+		But in the INSERT action of MERGE command, the VALUES list can have expressions with variables(attributes of the targe and source tables).
+		Besides, in the ordinary INSERT, a VALUES list can never be followed by a WHERE clause. But in MERGE INSERT action, there are matching conditions. 
+
+		Thus, the output qry of this function is an INSERT query in the style of "INSERT...VALUES...", except that we have other range tables and a WHERE clause.
+		Note that it is also different from the "INSERT ... SELECT..." query, in which the whole SELECT is a subquery. (We don't have subquery here).
+		We construct this novel query structure in order to keep consitency with other merge action types (DELETE, UPDATE). 
+		In this way, all the merge action queries are in fact share the very same Range Table, They only differs in their target lists and join trees
+		
+		*/
+		
+				
+		/*parse the action query, this will call transformInsertStmt() which analyzes the VALUES list.*/
+		actqry = transformStmt(pstate, (Node *)insertact);
+
+		/*do the WHERE clause here, Since the transformInsertStmt() function 
+		only analyzes the VALUES list but not the WHERE clause*/
+		qual = transformWhereClause(pstate,(Node *)act_qual_expr, "WHERE");
+		
+		actqry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_INSERT|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper INSERT action in merge stmt");
+
+
+		return actqry;
+	}
+	else
+		elog(ERROR, "unknown action type in MERGE");
+
+	/*never comes here*/
+	return NULL;
+}
+
+
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+	Query	   *qry; 
+
+	ColumnRef *starRef;
+	ResTarget *starResTarget;
+	ListCell *act;
+	ListCell *l;
+	JoinExpr *joinexp;
+	int 	rtindex;
+
+	/*this will never happen, since the garm.y is restricted that only one rel name is allowed to appear in the source table position. 
+	However, if we extent the command in future, we may need to note this check here. 
+	*/
+	if(list_length(stmt->source) != 1) 
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("now we only accept merge command with only ONE source table")));
+		
+	/*now, do the real tranformation of the merge command. */
+	qry = makeNode(Query);
+	qry->commandType = CMD_MERGE;
+
+	/*
+	What we are doing here is to create a query like 
+		"SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+	Note:	
+	1. we set the "match condition" as the join qualification. 
+	The left join will scan both the matched and non-matched tuples.
+
+	2. a normal SELECT query has no "target relation". 
+	But here we need to set the targe relation in query, like the UPDATE/DELETE/INSERT queries. 
+	So this is a left join SELECT with a "target table" in its range table. 
+
+	3. We don't have a specific ACL level for Merge, here we just use ACL_SELECT. 
+	But we will add other ACL levels when handle each merge actions.  
+	*/
+
+
+	/*before analyze the FROM clause, we need to set the target table. 
+	We cannot call setTargetTable() function directly. 
+	We only need the lock target relation, without adding it to Range table. 
+	*/
+	
+	
+	setTargetTableLock(pstate, stmt->relation);
+	
+	
+	
+	/*create the FROM clause. Make the join expression first*/
+	joinexp = makeNode(JoinExpr);
+	joinexp->jointype = JOIN_LEFT;
+	joinexp->isNatural = FALSE;
+	joinexp->larg = linitial(stmt->source);/*source list has only one element*/
+	joinexp->rarg = (Node *)stmt->relation;
+	joinexp->quals = stmt->matchCondition; /*match condtion*/
+
+	/*transform the FROM clause. The target relation and source relation will be add to Rtable here.	*/
+	transformFromClause(pstate, list_make1(joinexp));
+
+	/*the targetList of the main query is "*"	  
+	*/
+	starRef = makeNode(ColumnRef);	
+	starRef->fields = list_make1(makeNode(A_Star));					
+	starRef->location = 1;					
+
+	starResTarget = makeNode(ResTarget);					
+	starResTarget->name = NULL;					
+	starResTarget->indirection = NIL;					
+	starResTarget->val = (Node *)starRef;					
+	starResTarget->location = 1;
+	
+	qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+	/*we don't need the WHERE clause here. Set it null. */
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	/*now , we find out the RTE for the target relation, and do some unfinished jobs*/
+	rtindex = 1;
+	foreach(l, pstate->p_rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+		if(rte->relid == pstate->p_target_relation->rd_id) /*find the RTE*/
+		{
+			pstate->p_target_rangetblentry = rte;
+			rte->requiredPerms = ACL_SELECT;	
+			qry->resultRelation = rtindex;
+			break;
+		}
+		rtindex++;
+	}
+
+	if(pstate->p_target_rangetblentry == NULL)
+		elog(ERROR, "cannot find the RTE for target table");
+	
+
+	qry->rtable = pstate->p_rtable;
+
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	/*
+	 * Top-level aggregates are simply disallowed in MERGE
+	 */
+	if (pstate->p_hasAggs)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("cannot use aggregate function in top level of MERGE"),
+				 parser_errposition(pstate,
+									locate_agg_of_level((Node *) qry, 0))));
+	if (pstate->p_hasWindowFuncs)
+		ereport(ERROR,
+				(errcode(ERRCODE_WINDOWING_ERROR),
+				 errmsg("cannot use window function in MERGE"),
+				 parser_errposition(pstate,
+									locate_windowfunc((Node *) qry))));
+
+
+	/*
+		the main query is done.
+	then for each actions ,we transform it to a seperate query.
+	the action queries shares the exactly same range table with the main query. 
+	in other words, in the extra condtions of the sub actions, we don't allow involvement of new tables
+	*/
+	
+	qry->mergeActQry = NIL;
+
+	foreach(act,stmt->actions)
+	{
+		MergeConditionAction *mca = lfirst(act);
+		Query *actqry;
+
+		switch(mca->action->type)
+		{
+			case T_MergeDelete:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+				break;
+			case T_MergeUpdate:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+				break;
+			case T_MergeInsert:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;				
+				break;
+			default:
+				elog(ERROR, "unknown MERGE action type %d", mca->type);
+				break;
+
+		}
+
+
+		/*transform the act (and its condition) as a single query. Link it to the top-level query*/
+		actqry = transformMergeActions(pstate, stmt, mca);
+
+		/*since we don't invoke setTargetTable() in transformMergeActions(), we need to set actqry->resultRelation here
+		*/
+		actqry->resultRelation = qry->resultRelation;
+
+		qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+	}
+	
+	return qry;
+
+	
+}
+
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..abf772c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,10 @@ static TypeName *TableFuncTypeName(List *columns);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 
+%type <node>	MergeStmt  opt_and_condition  merge_condition_action   merge_action
+%type <boolean> opt_not	
+%type <list> 	merge_condition_action_list
+
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
 
@@ -504,6 +508,8 @@ static TypeName *TableFuncTypeName(List *columns);
 
 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
+	MATCHED MERGE
+	
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -725,6 +731,7 @@ stmt :
 			| ListenStmt
 			| LoadStmt
 			| LockStmt
+			| MergeStmt 
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -6952,6 +6959,7 @@ ExplainableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
+			| MergeStmt
 			| DeclareCursorStmt
 			| CreateAsStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
@@ -7297,6 +7305,101 @@ set_target_list:
 /*****************************************************************************
  *
  *		QUERY:
+ *				MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt: 
+			MERGE INTO relation_expr_opt_alias
+			USING  table_ref
+			ON a_expr
+			merge_condition_action_list
+				{
+					MergeStmt *m = makeNode(MergeStmt);
+
+					m->relation = $3;
+					m->source = list_make1($5);  /*although we have only one USING table, but we still make it a list, maybe in future we will allow mutliple USING tables.*/
+					m->matchCondition = $7;
+					m->actions = $8;
+
+					$$ = (Node *)m;
+				}
+				;
+	
+merge_condition_action_list: 
+							merge_condition_action 		
+								{ $$ = list_make1($1); }
+							| merge_condition_action_list merge_condition_action   
+								{ $$ = lappend($1,$2); }
+							;	
+
+merge_condition_action: 	
+							WHEN opt_not MATCHED opt_and_condition THEN merge_action
+							{
+								MergeConditionAction *m = makeNode(MergeConditionAction);
+
+								m->match = $2;
+								m->condition = $4;
+								m->action = $6;
+
+								$$ = (Node *)m;
+							}
+							;
+
+
+opt_and_condition:	
+					AND a_expr 		{$$ = $2;}
+					| /*EMPTY*/ 		{$$ = NULL;}
+					;
+
+opt_not:	
+			NOT			{$$ = false;}
+			| /*EMPTY*/	{$$ = true;}
+			;
+
+merge_action: 	
+				DELETE_P	
+					{$$ = (Node *)makeNode(MergeDelete);}
+				| UPDATE SET set_clause_list 
+					{
+						UpdateStmt *n = makeNode(MergeUpdate);
+						n->targetList = $3;
+						$$ = (Node *)n;
+					}
+				| INSERT values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = $2;
+
+						$$ = (Node *)n;
+					}
+					
+				| INSERT '(' insert_column_list ')' values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = $3;
+						n->selectStmt = $5;
+
+						$$ = (Node *)n;
+					}
+				| INSERT DEFAULT VALUES
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = NULL;
+
+						$$ = (Node *)n; 
+					}
+					
+				;	
+				
+
+
+/*****************************************************************************
+ *
+ *		QUERY:
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
@@ -10935,7 +11038,9 @@ unreserved_keyword:
 			| LOGIN_P
 			| MAPPING
 			| MATCH
+			| MATCHED
 			| MAXVALUE
+			| MERGE
 			| MINUTE_P
 			| MINVALUE
 			| MODE
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index f30132a..657dd9c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -213,6 +213,29 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	return rtindex;
 }
 
+
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+	
+	/* Close old target; this could only happen for multi-action rules */
+	if (pstate->p_target_relation != NULL)
+		heap_close(pstate->p_target_relation, NoLock);
+
+	/*
+	 * Open target rel and grab suitable lock (which we will hold till end of
+	 * transaction).
+	 *
+	 * free_parsestate() will eventually do the corresponding heap_close(),
+	 * but *not* release the lock.
+	 */
+	pstate->p_target_relation = parserOpenTable(pstate, relation,
+												RowExclusiveLock);
+
+	
+}
+
+
 /*
  * Simplify InhOption (yes/no/default) into boolean yes/no.
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..9e3b30b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1837,6 +1837,45 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 }
 
 
+#define insert_rewrite (1<<0) /*insert merge action has already been processed by rewriter*/
+#define delete_rewrite (1<<1) 
+#define update_rewrite (1<<2)
+
+#define	insert_instead (1<<3) /*insert merge action is fully replace by rules.*/
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+
+#define merge_action_already_rewrite(acttype, flag) \
+	((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+		(acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+		(acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+
+#define set_action_rewrite(acttype, flag)	\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_rewrite;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_rewrite;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_rewrite;}
+
+
+				
+#define merge_action_instead(acttype, flag)		\
+			((acttype == CMD_INSERT && (flag & insert_instead)) || \
+				(acttype == CMD_UPDATE && (flag & update_instead)) || \
+				(acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_instead;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_instead;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_instead;}
+
+
 /*
  * QueryRewrite -
  *	  Primary entry point to the query rewriter.
@@ -1861,7 +1900,149 @@ QueryRewrite(Query *parsetree)
 	 *
 	 * Apply all non-SELECT rules possibly getting 0 or many queries
 	 */
-	querylist = RewriteQuery(parsetree, NIL);
+
+	
+	if(parsetree->commandType == CMD_MERGE)
+	{
+		/*
+		*for merge query, we have a set of action queries (not subquery).
+		*each of these action queries should be applied to RewriteQuery(). 
+		*/
+		ListCell   *cell,
+				   *prev,
+				   *next;
+
+		int flag = 0;
+
+		
+		List *pre_qry = NIL;
+		List *post_qry = NIL;
+
+
+		querylist = NIL;
+		
+
+		/*rewrite the merge action queries one by one.*/
+		prev = NULL;
+
+		for (cell = list_head(parsetree->mergeActQry); cell; cell = next)
+		{
+			List *queryList4action = NIL;
+			Query *actionqry;
+			Query *q;
+
+			
+			actionqry = lfirst(cell);
+
+			next = lnext(cell);
+
+			/*if this kind of actions are fully replaced by rules, we delete it from the action list*/	
+			if(merge_action_instead(actionqry->commandType, flag))
+			{
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+
+			/*if this kind of actions are already processed by rewriter, skip it.*/
+			if(merge_action_already_rewrite(actionqry->commandType, flag))
+			{
+				/*	Still need to call RewriteQuery(), since we need the process on target list and so on. 
+					BUT, the returned list is discarded
+				*/
+				RewriteQuery(actionqry, NIL);
+			
+				prev = cell; 
+				continue;
+			}
+
+			/*ok this action has not been processed before, let's do it now.*/
+			queryList4action = RewriteQuery(actionqry, NIL);
+
+			/*this kind of actions has been processed.*/
+			set_action_rewrite(actionqry->commandType,flag); 
+
+			/*if the returning list is nil, this merge action is replaced by a do-nothing rule*/
+			if(queryList4action == NIL) 
+			{
+				/*set the flag for other merge actions of the same type*/
+				set_action_instead(actionqry->commandType, flag);
+
+				/*delete the action.*/
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+
+				continue;
+			}
+
+			
+			/*the merge action query could be one of the elements in the rewriten list. 
+			*if it is in the list, it must be the head or tail.
+			*/
+			q = (Query *)linitial(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*
+				*the merge action is the head, the remaining part of the list are the queries generated by rules
+				*we put them in the post_qry list. 
+				*/
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+
+
+				queryList4action = list_delete_first(queryList4action);
+				post_qry = list_concat(post_qry,queryList4action);
+				prev = cell; 
+				continue;
+
+			}
+			
+			
+			q = (Query *)llast(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the tail. Put the rule queries in pre_qry list*/
+
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+					
+				queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+				pre_qry = list_concat(pre_qry,queryList4action);
+
+				prev = cell; 
+				continue;
+
+			}	
+				
+
+			/*here, the merge action query is not in the rewriten query list, 
+			*which means the action should be deleted.
+			*It is replaced by INSTEAD rule(s). We need to delete the action  
+
+				For a INSERT action, we put the rule queries in the post list
+				otherwise, in the pre list
+			*/
+			if(actionqry->commandType == CMD_INSERT)
+				post_qry = list_concat(post_qry,queryList4action);
+			else
+				pre_qry = list_concat(pre_qry,queryList4action);
+			
+			set_action_instead(actionqry->commandType, flag);
+			parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+		}				
+
+		
+
+		/*finally, put the 3 lists into one. 
+		*If all the merge actions are replaced by rules, the original merge query 
+		*will not be involved in the querylist.
+		*/
+		querylist = list_concat(pre_qry,querylist);
+		querylist = list_concat(querylist, post_qry);
+			
+	}
+	else
+		querylist = RewriteQuery(parsetree, NIL);
 
 	/*
 	 * Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
 				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
 						 "DELETE %u", queryDesc->estate->es_processed);
 				break;
+			case CMD_MERGE:
+				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+						 "MERGE %u", queryDesc->estate->es_processed);
+				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8960246..2733e5d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
 			case CMD_UPDATE:
 			case CMD_INSERT:
 			case CMD_DELETE:
+			case CMD_MERGE:
 				return false;
 			default:
 				elog(WARNING, "unrecognized commandType: %d",
@@ -1398,6 +1399,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_MergeStmt:
+			tag = "MERGE";
+			break;
+		
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2235,6 +2240,7 @@ GetCommandLogLevel(Node *parsetree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
+		case T_MergeStmt:
 			lev = LOGSTMT_MOD;
 			break;
 
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..92f50ed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,9 @@
 #include "nodes/execnodes.h"
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+
+extern MergeActionState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
+
 extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 09fdb5d..cfd09e6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1031,9 +1031,22 @@ typedef struct ModifyTableState
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
+	List 		*mergeActPstates; /*the list of the planstate of meger command actions. 
+									NIL if this is not a merge command.
+									The elements if it are MergeActionState nodes*/
 } ModifyTableState;
 
 /* ----------------
+ *	 MergeActionState information
+ * ----------------
+ */
+typedef struct MergeActionState
+{
+	PlanState	ps;				/* its first field is NodeTag */
+	CmdType		operation;	
+} MergeActionState;
+
+/* ----------------
  *	 AppendState information
  *
  *		nplans			how many plans are in the array
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..053eee7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
 	T_Plan = 100,
 	T_Result,
 	T_ModifyTable,
+	T_MergeAction,
 	T_Append,
 	T_RecursiveUnion,
 	T_BitmapAnd,
@@ -86,6 +87,7 @@ typedef enum NodeTag
 	T_PlanState = 200,
 	T_ResultState,
 	T_ModifyTableState,
+	T_MergeActionState,
 	T_AppendState,
 	T_RecursiveUnionState,
 	T_BitmapAndState,
@@ -347,6 +349,14 @@ typedef enum NodeTag
 	T_AlterUserMappingStmt,
 	T_DropUserMappingStmt,
 	T_AlterTableSpaceOptionsStmt,
+		
+	T_MergeStmt,
+	T_MergeConditionAction,
+	T_MergeUpdate, 
+	T_MergeDelete,	
+	T_MergeInsert,
+		
+		
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -511,6 +521,7 @@ typedef enum CmdType
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
 	CMD_DELETE,
+	CMD_MERGE,						/*merge stmt*/
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b591073..8638a42 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -146,6 +146,11 @@ typedef struct Query
 
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
+
+	bool		 isMergeAction; /*if this query is a merge action. */	 
+
+	List 		*mergeActQry; /* the list of all the merge actions. 
+								* used only for merge query statment*/							 
 } Query;
 
 
@@ -990,6 +995,44 @@ typedef struct SelectStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
+/*The structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+	NodeTag		type;
+	RangeVar   	*relation;		/*targe relation for merge */
+
+	/* source relations for the merge. 
+	*Currently, we only allwo single-source merge, 
+	*so the length of this list should always be 1 
+	*/
+	List		*source;		
+	Node	   	*matchCondition;	/* qualifications of the merge*/
+
+	/*list  of MergeConditionAction structure. 
+	*It stores all the matched / not-matched conditions and the corresponding actions
+	*The elments of this list are MergeConditionAction nodes
+	*/
+	List	   	*actions;		
+
+}MergeStmt;
+
+/*	the structure for the actions of MERGE command. 
+*	Holds info of the clauses like "... WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+	NodeTag		type;
+	bool 		match; /*match or not match*/
+	Node		*condition;/*the AND condition for this action*/
+	Node 		*action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+/*
+*	The merge action nodes are in fact the ordinary nodes of UPDATE,DELETE and INSERT
+*/
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
 
 /* ----------------------
  *		Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..5a73d60 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,9 +169,22 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List 		*mergeActPlan;	/*the plans for merge actions, which are also ModifyTable nodes*/
 } ModifyTable;
 
 /* ----------------
+ *	 MergeAction node -
+ *		The plan node for the actions of MERGE command
+ * ----------------
+ */
+typedef struct MergeAction
+{
+	Plan plan;
+	CmdType	operation;/* INSERT, UPDATE, or DELETE */
+	List *flattenedqual; /*the flattened qual expression of action*/
+}MergeAction;
+
+/* ----------------
  *	 Append node -
  *		Generate the concatenation of the results of sub-plans.
  * ----------------
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 805dee7..6d7ff4f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -34,6 +34,7 @@ extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
 				 bool hasRecursion, double tuple_fraction,
 				 PlannerInfo **subroot);
+extern ModifyTable *merge_action_planner(PlannerGlobal *glob, Query *parse, Plan *top_plan);
 
 extern Expr *expression_planner(Expr *expr);
 
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index b0e04a0..4d6c9e8 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -15,6 +15,7 @@
 #define VAR_H
 
 #include "nodes/relation.h"
+#include "nodes/plannodes.h"
 
 typedef enum
 {
@@ -32,5 +33,5 @@ extern int	locate_var_of_relation(Node *node, int relid, int levelsup);
 extern int	find_minimum_var_level(Node *node);
 extern List *pull_var_clause(Node *node, PVCPlaceHolderBehavior behavior);
 extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
-
+extern void push_up_merge_action_vars(MergeAction * actplan,Query * actqry);
 #endif   /* VAR_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..436d459 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -229,7 +229,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..e4312d8 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,8 @@
 extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
+
 extern bool interpretInhOption(InhOption inhOpt);
 extern bool interpretOidsOption(List *defList);
 
#12Josh Berkus
josh@agliodbs.com
In reply to: Boxuan Zhai (#11)
Re: merge command - GSoC progress

And, I have tested the running of MERGE command with different
situations. I am sorry that I didn't create regression test files,
because I am not sure how to add new files in the git package. But, I
have written web pages in Postgres Wiki. I explain the details of my
implementation and a set of testing examples.

Can someone help Boxuan with how to write regression tests?

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#13Josh Berkus
josh@agliodbs.com
In reply to: Boxuan Zhai (#11)
#14David Fetter
david@fetter.org
In reply to: Josh Berkus (#12)
Re: merge command - GSoC progress

On Tue, Aug 03, 2010 at 03:14:02PM -0700, Josh Berkus wrote:

And, I have tested the running of MERGE command with different
situations. I am sorry that I didn't create regression test files,
because I am not sure how to add new files in the git package.
But, I have written web pages in Postgres Wiki. I explain the
details of my implementation and a set of testing examples.

Can someone help Boxuan with how to write regression tests?

Happy to. I'll start this evening PDT or tomorrow :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#15Greg Smith
greg@2ndquadrant.com
In reply to: Boxuan Zhai (#11)
Re: merge command - GSoC progress

Boxuan Zhai wrote:

I think there are no redundant lines in this time's patch file.

It is much better. There are still more blank likes around the new code
you've added than are needed in many places, but that doesn't interfere
with reading the patch.

The main code formatting issue left you'll need to address eventually
are all the really long comments in there.

And, I have tested the running of MERGE command with different
situations. I am sorry that I didn't create regression test files,
because I am not sure how to add new files in the git package.

git add <filename> ?

The tests you've put in there are the right general sort of things to
try out. The one example you gave does show an UPSERT being emulated by
MERGE, which is the #1 thing people are looking for initially.

--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us

#16Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Greg Smith (#15)
1 attachment(s)
Re: merge command - GSoC progress

2010/8/4 Greg Smith <greg@2ndquadrant.com>

Boxuan Zhai wrote:

I think there are no redundant lines in this time's patch file.

It is much better. There are still more blank likes around the new code
you've added than are needed in many places, but that doesn't interfere with
reading the patch.

Sorry, it is my personal habit of leaving blanks around codes. I will chage

this if it doesn't follow the pgsql coding style.

The main code formatting issue left you'll need to address eventually are
all the really long comments in there.

I will correct the long comments in the next patch.

And, I have tested the running of MERGE command with different situations.

I am sorry that I didn't create regression test files, because I am not sure
how to add new files in the git package.

git add <filename> ?

The tests you've put in there are the right general sort of things to try
out. The one example you gave does show an UPSERT being emulated by MERGE,
which is the #1 thing people are looking for initially.

In fact, I have created a merge.sql with simple merge example. I put it in

the folder of /src/test/regress/sql/ and modified the serial_schedule file
to add a line of : test: merge
Is this correct?

But, I don't know how to run regress to test this sql file. My "make check"
fails when install the db. I think this is because I do it under a MinGW
environment and some parameters are not matched with the default setting of
postgres.
I can configure, make install , initdb and do sql query in psql successfully
in my machine. So the source code itself should be correct.

I put my merge.sql in attachment, in case anyone want to have a look.

Show quoted text

--
Greg Smith 2ndQuadrant US Baltimore, MD
PostgreSQL Training, Services and Support
greg@2ndQuadrant.com www.2ndQuadrant.us <http://www.2ndquadrant.us/&gt;

Attachments:

merge.sqlapplication/octet-stream; name=merge.sqlDownload
#17Boxuan Zhai
bxzhai2010@gmail.com
In reply to: Greg Smith (#15)
Re: merge command - GSoC progress

Dear Robert,

I am just considering that there may be some logical mistakes for my rule
rewriting strategy of MERGE actions.

In my current design, if we find that an action type, say UPDATE, is
replaced by INSTEAD rules, we will remove all the actions of this type from
the MERGE command, as if they are not be specified by user from the
beginning. See the test example in my pages for this situation.
https://wiki.postgresql.org/wiki/MergeTestExamples#With_INSTEAD_rules

Now,I am thinking that maybe we should keep the replaced actions in action
list, and just mark them to be "invalid". If one join tuple from the main
plan fits the condition of this action, we will do nothing on it.

This strategy is a little bit different with the current one. If we delete
an action, the tuples that meet it condition will be caught by other
actions. If we keep it, the tuples that match it will be skipped.

I think the new design is more logical, and I wonder your opinion on this
problem.

Yours Boxuan

#18Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#16)
1 attachment(s)
Re: merge command - GSoC progress

On 04/08/10 07:55, Boxuan Zhai wrote:

2010/8/4 Greg Smith<greg@2ndquadrant.com>

Boxuan Zhai wrote:

And, I have tested the running of MERGE command with different situations.
I am sorry that I didn't create regression test files, because I am not sure
how to add new files in the git package.

git add<filename> ?

Yep. I believe Boxuan is using git in a simplistic way, doing just "git
diff" to create patches. For adding new files, you need to do "git add
<filename>", but note that this adds the new file to "staging area". To
view all changes in the staging area, use "git diff --cached", but that
won't show any modifications to existing files that you haven't also
"git add"ed. So to generate a patch you need to "git add" all modified
and added files ("git add -u" will add all modified files
automatically), and then use "git diff --cached" to generate the diff.

In fact, I have created a merge.sql with simple merge example. I put it in
the folder of /src/test/regress/sql/ and modified the serial_schedule file
to add a line of : test: merge
Is this correct?

Yep. You also need to add it to parallel_schedule, "make check" and
"make installcheck-parallel" use parallel_schedule and "make
installcheck" uses serial_schedule.

But, I don't know how to run regress to test this sql file. My "make check"
fails when install the db. I think this is because I do it under a MinGW
environment and some parameters are not matched with the default setting of
postgres.
I can configure, make install , initdb and do sql query in psql successfully
in my machine. So the source code itself should be correct.

Hmm, I don't know much about MinGW, but "make check" should work. But
since you can install and run postgres normally, you can use "make
installcheck" instead. That runs the regression suite against an already
running instance of postgres.

I put my merge.sql in attachment, in case anyone want to have a look.

Thanks, that helps a lot.

Here's a combined patch which includes the latest code patch from
"tested_merge.tar", with the merge.sql test case, the corresponding
expected output file, and the required changes to the schedule files.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

merge-command-2010-08-03.patchtext/x-diff; name=merge-command-2010-08-03.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b776ad1..cdb492b 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -73,6 +73,8 @@ static void show_sort_keys(SortState *sortstate, List *ancestors,
 static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
 static const char *explain_get_index_name(Oid indexId);
+static void ExplainMergeActions(ModifyTableState *mt_planstate, 
+									List *ancestors, ExplainState *es);
 static void ExplainScanTarget(Scan *plan, ExplainState *es);
 static void ExplainMemberNodes(List *plans, PlanState **planstates,
 				   List *ancestors, ExplainState *es);
@@ -636,6 +638,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				case CMD_DELETE:
 					pname = operation = "Delete";
 					break;
+				case CMD_MERGE:
+					pname = operation = "Merge";
+					break;
 				default:
 					pname = "???";
 					break;
@@ -1190,6 +1195,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	switch (nodeTag(plan))
 	{
 		case T_ModifyTable:
+			ExplainMergeActions((ModifyTableState *)planstate, ancestors, es);
+			
 			ExplainMemberNodes(((ModifyTable *) plan)->plans,
 							   ((ModifyTableState *) planstate)->mt_plans,
 							   ancestors, es);
@@ -1482,6 +1489,55 @@ explain_get_index_name(Oid indexId)
 	return result;
 }
 
+static void
+ExplainMergeActions(ModifyTableState *mt_planstate, List *ancestors, ExplainState *es)
+{
+	ListCell *l;
+	
+	if(mt_planstate->operation != CMD_MERGE || mt_planstate->mergeActPstates == NIL)
+		return;
+
+	foreach(l,mt_planstate->mergeActPstates)
+	{
+		ModifyTableState *mt_state = (ModifyTableState *)lfirst(l);
+
+		MergeActionState *act_pstate = (MergeActionState *)mt_state->mt_plans[0];
+
+		MergeAction *act_plan = (MergeAction *)act_pstate->ps.plan;
+
+		switch(act_pstate->operation)
+		{
+			case CMD_INSERT:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Insert When Not Matched And",es);
+				else
+					ExplainPropertyText("Action", "Insert When Not Matched",es);				
+				break;
+			case CMD_UPDATE:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Update When Matched And",es);
+				else
+					ExplainPropertyText("Action", "Update When Matched",es);	
+				break;
+			case CMD_DELETE:
+				if(act_plan->flattenedqual)
+					ExplainPropertyText("Action", "Delete When Matched And",es);
+				else
+					ExplainPropertyText("Action", "Delete When Matched",es);	
+				break;	
+			default:
+				elog(ERROR, "unknown merge action type");
+		}
+
+		/*
+		*	We need a faltened qual expression for show_qual
+		*/
+		show_qual(act_plan->flattenedqual, "  qual", &act_pstate->ps, ancestors, true, es);
+	}
+	
+}
+
+
 /*
  * Show the target of a Scan node
  */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8cd3ae9..9a2db5f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -170,6 +170,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_INSERT:
 		case CMD_DELETE:
 		case CMD_UPDATE:
+		case CMD_MERGE:	
 			estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index f4cc7d9..2d1a4e7 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -153,6 +153,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_MergeAction:
+			result = (PlanState *) ExecInitMergeAction((MergeAction*) node,
+													   estate, eflags);
+			break;
+			
 		case T_Append:
 			result = (PlanState *) ExecInitAppend((Append *) node,
 												  estate, eflags);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8619ce3..bd84784 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -583,6 +583,116 @@ lreplace:;
 }
 
 
+static TupleTableSlot *
+ExecMerge(ItemPointer tupleid,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   ModifyTableState *node,
+		   EState *estate)
+{
+
+	TupleTableSlot *actslot = NULL;
+	TupleTableSlot *res = NULL;
+	ListCell *each;
+
+	/*
+	*	try the merge actions one by one
+	*/
+	foreach(each, node->mergeActPstates)
+	{
+		ModifyTableState *mt_pstate;
+
+		MergeActionState *action_pstate; 
+
+		ExprContext *econtext;
+
+		
+		mt_pstate = (ModifyTableState *)lfirst(each);
+
+		/*
+		*	mt_pstate is supposed to have only ONE mt_plans, 
+		*	which is a MergeActionState
+		*/
+		Assert(mt_pstate->mt_nplans == 1);
+
+		action_pstate = (MergeActionState *)mt_pstate->mt_plans[0];
+
+		/*
+		*	If tupleid == NULL, it is a NOT MATCHED case, in which we can only do INSERT action
+		*	If tupleid != NULL, it is a MATCHED case, in which we can only do UPDATE or DELETE action
+		*/		
+		if((tupleid == NULL && action_pstate->operation != CMD_INSERT) ||
+			(tupleid != NULL && action_pstate->operation == CMD_INSERT))
+		{
+			continue;
+		}
+
+		/*
+		*	Setup the expression context
+		*/
+		econtext = action_pstate->ps.ps_ExprContext;
+
+		/*
+		If the action has an additional qual, which is not satisfied, skip it
+		*/	
+		
+		if(action_pstate->ps.qual) 
+		{	
+			ResetExprContext(econtext);
+		
+			econtext->ecxt_scantuple = slot;
+			econtext->ecxt_outertuple = planSlot;
+
+			if(!ExecQual(action_pstate->ps.qual, econtext,false))
+			{	
+				continue;
+			}
+		}
+		/*
+		*	Now we start to exec this action. Firstly , project the result tuple slot. 
+		*	The merge action result slot is projected in the same way as a returning slot. 
+		*	so we call ExecProcessReturning() directly here.
+		*/
+		if(action_pstate->operation != CMD_DELETE)
+			actslot = ExecProcessReturning(action_pstate->ps.ps_ProjInfo, slot, planSlot);
+
+		switch (action_pstate->operation)
+		{
+			case CMD_INSERT:
+				res = ExecInsert(actslot, planSlot, estate);
+				break;
+			case CMD_UPDATE:
+				res = ExecUpdate(tupleid,
+							actslot,
+							planSlot,
+							&mt_pstate->mt_epqstate,
+							estate);
+				break;
+			case CMD_DELETE:
+				res = ExecDelete(tupleid, 
+							planSlot, 
+							&mt_pstate->mt_epqstate,
+							estate);
+				break;
+			default:
+				elog(ERROR, "unknown merge action type");
+				break;
+		}
+
+
+		/*
+		* The current action is executed, no need to do the remain actions
+		*/	
+		return res;
+	}
+
+	/*
+	* Here, no action is taken. Let's pass this slot
+	*/	
+	return NULL;
+	
+}
+
 /*
  * Process BEFORE EACH STATEMENT triggers
  */
@@ -603,6 +713,9 @@ fireBSTriggers(ModifyTableState *node)
 			ExecBSDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the BS triggers of merge\n");
+			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -629,6 +742,9 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state,
 								 node->ps.state->es_result_relations);
 			break;
+		case CMD_MERGE:
+			printf("not sure about the AS triggers of merge\n");
+			break;	
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -708,20 +824,34 @@ ExecModifyTable(ModifyTableState *node)
 			/*
 			 * extract the 'ctid' junk attribute.
 			 */
-			if (operation == CMD_UPDATE || operation == CMD_DELETE)
+			if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 			{
 				Datum		datum;
 				bool		isNull;
 
 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
 											 &isNull);
-				/* shouldn't ever get a null result... */
+				
 				if (isNull)
-					elog(ERROR, "ctid is NULL");
-
-				tupleid = (ItemPointer) DatumGetPointer(datum);
-				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
-				tupleid = &tuple_ctid;
+				{
+					/*
+					*	shouldn't ever get a null result for update and delete. 
+					*	Merge command will get a null ctid in "NOT MATCHED" case
+					*/
+					if(operation != CMD_MERGE)
+						elog(ERROR, "ctid is NULL");
+					else
+						tupleid = NULL;
+				}	
+				else
+				{
+				
+					tupleid = (ItemPointer) DatumGetPointer(datum);
+					
+					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
+					tupleid = &tuple_ctid;
+					
+				}
 			}
 
 			/*
@@ -744,6 +874,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecDelete(tupleid, planSlot,
 								  &node->mt_epqstate, estate);
 				break;
+			case CMD_MERGE:
+				slot = ExecMerge(tupleid, slot, planSlot,
+								  node, estate);
+				break;	
+				
 			default:
 				elog(ERROR, "unknown operation");
 				break;
@@ -771,6 +906,86 @@ ExecModifyTable(ModifyTableState *node)
 	return NULL;
 }
 
+
+/*
+*	When init a merge plan, we also need init its action plans. 
+*	These action plans are "MergeAction" plans (new plan node for the merge actions).
+*	
+*	In this function, we only want to handle the tlist and qual in the plan.
+*	The returning result is a  "MergeActionState".  
+*/
+MergeActionState *
+ExecInitMergeAction(MergeAction *node, EState *estate, int eflags)
+{
+	MergeActionState *result;
+	
+	/*
+	 * do nothing when we get to the end of a leaf on tree.
+	 */
+	if (node == NULL)
+		return NULL;
+
+		
+	/*
+	 * create state structure
+	 */
+	result = makeNode(MergeActionState);
+	result->operation = node->operation;
+	result->ps.plan = (Plan *)node;
+	result->ps.state = estate;
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &result->ps);
+
+
+	
+	/*
+	 * initialize tuple type 
+	 */
+	ExecAssignResultTypeFromTL(&result->ps);
+
+		
+	/*
+	 * create expression context for node
+	 */
+	 
+	ExecAssignExprContext(estate, &result->ps);
+
+
+	/*
+	 * initialize child expressions
+	 */
+	result->ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->plan.targetlist,  &result->ps);
+
+	
+	result->ps.qual = (List *)
+		ExecInitExpr((Expr *) node->plan.qual, &result->ps);
+
+	
+	/*
+	* init the projection information
+	*/
+	ExecAssignProjectionInfo(&result->ps, NULL);
+
+	/*
+	do we need a check for the plan output here ?
+	
+	if(estate->es_result_relation_info == NULL)
+		printf("re infro is null\n");
+
+	if(estate->es_result_relation_info->ri_RelationDesc == NULL)
+		printf("r relation desc is nUll\n");
+		ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+								node->targetlist);
+	printf("finish check plan output for merge actio \n");	
+	*/
+	return result;
+}
+
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
@@ -786,6 +1001,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	bool 		isMergeAction = false;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -826,6 +1042,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	foreach(l, node->plans)
 	{
 		subplan = (Plan *) lfirst(l);
+
+		/*
+		* 	test if this subplan node is a MergeAction. 
+		* 	We need this information for setting the junckfilter. 
+		*	juckfiler is necessary for an ordinary UPDATE/DELETE plan, 
+		*	but not for an UPDATE/DELETE merge action  
+		*/		
+		if(IsA(subplan, MergeAction))
+			isMergeAction = true;
+		
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 		estate->es_result_relation_info++;
 		i++;
@@ -955,7 +1181,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
-				junk_filter_needed = true;
+			case CMD_MERGE:	
+				if(!isMergeAction)
+					junk_filter_needed = true;
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -978,9 +1206,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 							resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
 									   ExecInitExtraTupleSlot(estate));
 
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
+				if (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)
 				{
-					/* For UPDATE/DELETE, find the ctid junk attr now */
+					/* For UPDATE/DELETE/MERGE, find the ctid junk attr now */
 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
 						elog(ERROR, "could not find junk ctid column");
@@ -1006,6 +1234,21 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (estate->es_trig_tuple_slot == NULL)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	
+	/*
+	* for the merge actions, we need to similar things as above
+	*/
+
+	foreach(l, node->mergeActPlan)
+	{
+		PlanState *actpstate = ExecInitNode((Plan *)lfirst(l),  estate, 0);
+		/*
+		* put the pstates of each action into ModifyTableState
+		*/
+		mtstate->mergeActPstates = lappend(mtstate->mergeActPstates, actpstate);
+		
+	}
+	
 	return mtstate;
 }
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9af1217..c12e7d8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -176,6 +176,7 @@ _copyModifyTable(ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(mergeActPlan);
 
 	return newnode;
 }
@@ -2273,6 +2274,10 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
 
+	COPY_SCALAR_FIELD(isMergeAction);
+	/*merge action list*/
+	COPY_NODE_FIELD(mergeActQry); 
+
 	return newnode;
 }
 
@@ -2343,6 +2348,64 @@ _copySelectStmt(SelectStmt *from)
 	return newnode;
 }
 
+
+static MergeStmt *
+_copyMergeStmt(MergeStmt *from)
+{
+	MergeStmt *newnode = makeNode(MergeStmt);
+
+	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(source);
+	COPY_NODE_FIELD(matchCondition);
+	COPY_NODE_FIELD(actions);
+	
+	return newnode;
+	
+}
+
+
+static MergeConditionAction *
+_copyMergeConditionAction(MergeConditionAction *from)
+{
+	MergeConditionAction *newnode = makeNode(MergeConditionAction);
+
+	COPY_SCALAR_FIELD(match);
+	COPY_NODE_FIELD(condition);
+	COPY_NODE_FIELD(action);
+
+	return newnode;
+}
+
+static MergeUpdate *
+_copyMergeUpdate(MergeUpdate *from)
+{
+	MergeUpdate *newNode = (MergeUpdate *)_copyUpdateStmt((UpdateStmt *) from);
+	newNode->type = T_MergeUpdate;
+
+	return newNode;
+}
+
+static MergeInsert *
+_copyMergeInsert(MergeInsert *from)
+{
+	MergeInsert *newNode = (MergeInsert *)_copyInsertStmt((InsertStmt *) from);
+	newNode->type = T_MergeInsert;
+
+	return newNode;
+}
+
+
+static MergeDelete *
+_copyMergeDelete(MergeDelete *from)
+{
+	MergeDelete *newNode = (MergeDelete *)_copyDeleteStmt((DeleteStmt *) from);
+	newNode->type = T_MergeDelete;
+
+	return newNode;
+}
+
+
+
 static SetOperationStmt *
 _copySetOperationStmt(SetOperationStmt *from)
 {
@@ -3904,6 +3967,21 @@ copyObject(void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_MergeStmt:
+			retval = _copyMergeStmt(from);
+			break;
+		case T_MergeConditionAction:
+			retval = _copyMergeConditionAction(from);
+			break;
+		case T_MergeUpdate:
+			retval = _copyMergeUpdate(from);
+			break;
+		case T_MergeInsert:
+			retval = _copyMergeInsert(from);
+			break;
+		case T_MergeDelete:
+			retval = _copyMergeDelete(from);
+			break;
 		case T_SetOperationStmt:
 			retval = _copySetOperationStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 70b3c62..cd7ee2b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -878,6 +878,7 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
 
+	COMPARE_SCALAR_FIELD(isMergeAction);
 	return true;
 }
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0454aa5..f9690a4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -331,6 +331,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(mergeActPlan);
 }
 
 static void
@@ -2020,8 +2021,60 @@ _outQuery(StringInfo str, Query *node)
 	WRITE_NODE_FIELD(limitCount);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(setOperations);
+	WRITE_BOOL_FIELD(isMergeAction);
+	WRITE_NODE_FIELD(mergeActQry);
 }
 
+
+static void
+_outMergeConditionAction(StringInfo str, MergeConditionAction *node)
+{
+	WRITE_NODE_TYPE("MERGECONDITIONACTION");
+
+	WRITE_BOOL_FIELD(match);
+	
+	WRITE_NODE_FIELD(condition);
+	WRITE_NODE_FIELD(action);
+
+
+}
+
+static void
+_outMergeStmt(StringInfo str, MergeStmt *node)
+{
+	WRITE_NODE_TYPE("MERGESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(source);
+	WRITE_NODE_FIELD(matchCondition);
+	WRITE_NODE_FIELD(actions);
+
+}
+
+static void
+_outMergeAction(StringInfo str, MergeAction*node)
+{
+	_outPlanInfo(str, (Plan *)node);
+
+	WRITE_ENUM_FIELD(operation, CmdType);
+
+}
+
+static void 
+_outDeleteStmt(StringInfo str, DeleteStmt *node)
+{
+	WRITE_NODE_TYPE("DELETESTMT");
+
+	WRITE_NODE_FIELD(relation);
+	WRITE_NODE_FIELD(usingClause);
+	WRITE_NODE_FIELD(whereClause);
+	WRITE_NODE_FIELD(returningList);
+
+
+}
+
+
+
 static void
 _outSortGroupClause(StringInfo str, SortGroupClause *node)
 {
@@ -2905,6 +2958,19 @@ _outNode(StringInfo str, void *obj)
 			case T_XmlSerialize:
 				_outXmlSerialize(str, obj);
 				break;
+			case T_MergeAction:
+				_outMergeAction(str, obj);
+				break;
+			case T_MergeStmt:
+				_outMergeStmt(str, obj);
+				break;
+			case T_MergeConditionAction:
+				_outMergeConditionAction(str,obj);
+				break;
+			case T_DeleteStmt:
+				_outDeleteStmt(str,obj);
+				break;
+					
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bc6e2a6..b792132 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -218,7 +218,9 @@ _readQuery(void)
 	READ_NODE_FIELD(limitCount);
 	READ_NODE_FIELD(rowMarks);
 	READ_NODE_FIELD(setOperations);
-
+	READ_BOOL_FIELD(isMergeAction);
+	READ_NODE_FIELD(mergeActQry);
+	
 	READ_DONE();
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3950ab4..0057547 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -565,6 +565,26 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											 returningLists,
 											 rowMarks,
 											 SS_assign_special_param(root));
+
+			/*do a simple plan for each actions in the merge command.
+			*put them in mergeActPlan list;
+			*/
+			if(parse->commandType == CMD_MERGE)
+			{
+				ListCell *l;
+				ModifyTable *top_plan = (ModifyTable *)plan;
+				foreach(l, parse->mergeActQry)
+				{
+					
+					Plan *actplan = (Plan *)merge_action_planner(glob, 
+																		(Query *)lfirst(l),
+																		(Plan *)linitial(top_plan->plans)
+																	);
+
+					top_plan->mergeActPlan = lappend(top_plan->mergeActPlan, actplan);
+				}
+
+			}
 		}
 	}
 
@@ -584,6 +604,109 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	return plan;
 }
 
+
+
+ModifyTable *
+merge_action_planner(PlannerGlobal *glob, Query *parse,
+				 Plan *top_plan)
+{
+	PlannerInfo *root;
+	MergeAction	*actplan;
+	ModifyTable *result;
+	
+	List	   	*returningLists;
+	List 		*rowMarks;
+
+	/*
+	 * no having clause in a merge action
+	 */
+	Assert(parse->havingQual == NULL);
+
+
+	/* Create a PlannerInfo data structure for this subquery */
+	root = makeNode(PlannerInfo);
+	root->parse = parse;
+	root->glob = glob;
+	root->query_level = 1;
+	root->parent_root = NULL;
+	root->planner_cxt = CurrentMemoryContext;
+	root->init_plans = NIL;
+	root->cte_plan_ids = NIL;
+	root->eq_classes = NIL;
+	root->append_rel_list = NIL;
+	root->hasPseudoConstantQuals = false;
+	root->hasRecursion = false;
+	root->wt_param_id = -1;
+	root->non_recursive_plan = NULL;
+
+
+	/*
+	*	Create the action plan node
+	*/
+	actplan = makeNode(MergeAction);
+	actplan->operation = parse->commandType;
+	
+	/*
+	 * Do expression preprocessing on targetlist and quals.
+	 */
+	parse->targetList = (List *)
+		preprocess_expression(root, (Node *) parse->targetList,
+							  EXPRKIND_TARGET);
+
+	preprocess_qual_conditions(root, (Node *) parse->jointree);
+	
+
+	/*
+	*	we need a flat qual for explaining
+	*/	
+	actplan->flattenedqual = flatten_join_alias_vars(root, parse->jointree->quals);
+	
+	/*copy the cost from the top_plan*/
+	actplan->plan.startup_cost = top_plan->startup_cost;
+	actplan->plan.total_cost = top_plan->total_cost;
+	actplan->plan.plan_rows = top_plan->plan_rows;
+	actplan->plan.plan_width = top_plan->plan_width;
+	
+	/*
+	*	prepare the result 
+	*/
+	actplan->plan.targetlist = preprocess_targetlist(root,parse->targetList);
+
+	actplan->plan.qual = (List *)parse->jointree->quals;
+	push_up_merge_action_vars(actplan, parse);
+
+	if (parse->returningList)
+	{
+		List	   *rlist;
+
+		Assert(parse->resultRelation);
+		rlist = set_returning_clause_references(root->glob,
+												parse->returningList,
+												&actplan->plan,
+											  parse->resultRelation);
+		returningLists = list_make1(rlist);
+	}
+	else
+		returningLists = NIL;
+
+
+	if (parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
+	
+	result = make_modifytable(parse->commandType,
+										   copyObject(root->resultRelations),
+											 list_make1(actplan),
+											 returningLists,
+											 rowMarks,
+											 SS_assign_special_param(root));
+
+	
+	return result;
+}
+
+
 /*
  * preprocess_expression
  *		Do subquery_planner's preprocessing work for an expression,
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 59d3518..5685d52 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -78,13 +78,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 								  result_relation, range_table);
 
 	/*
-	 * for "update" and "delete" queries, add ctid of the result relation into
+	 * for "update" , "delete"  and "merge" queries, add ctid of the result relation into
 	 * the target list so that the ctid will propagate through execution and
 	 * ExecutePlan() will be able to identify the right tuple to replace or
 	 * delete.	This extra field is marked "junk" so that it is not stored
 	 * back into the tuple.
+	 *  
+	 * BUT, if the query node is a merge action, we don't need to expend the ctid attribute in tlist.
+	 * The tlist of the merge top level plan already contains a "ctid" junk attr of the target relation.
 	 */
-	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+	 
+	if(!parse->isMergeAction  &&  
+			(command_type == CMD_UPDATE || 
+			command_type == CMD_DELETE || 
+			command_type == CMD_MERGE))
 	{
 		TargetEntry *tle;
 		Var		   *var;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 92c2208..f1fcbaa 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -67,6 +67,16 @@ typedef struct
 	bool		inserted_sublink;		/* have we inserted a SubLink? */
 } flatten_join_alias_vars_context;
 
+typedef struct
+{
+	int varno_source;
+	int varno_target;
+	int varno_join;
+
+	int offset_source;
+	int offset_target;
+} push_up_merge_action_vars_context;
+
 static bool pull_varnos_walker(Node *node,
 				   pull_varnos_context *context);
 static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
@@ -83,6 +93,8 @@ static bool pull_var_clause_walker(Node *node,
 static Node *flatten_join_alias_vars_mutator(Node *node,
 								flatten_join_alias_vars_context *context);
 static Relids alias_relid_set(PlannerInfo *root, Relids relids);
+static bool push_up_merge_action_vars_walker(Node *node, 
+								push_up_merge_action_vars_context *context);
 
 
 /*
@@ -677,6 +689,88 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 								  (void *) context);
 }
 
+/*
+*	When prepare for the MERGE command, we have made a left join between the 
+*	source table and target table as the main plan. In this case, the range table contains 
+*	ONLY THREE range table entries: 
+*	The first one is that of the source table, which may be a subquery or a plain table
+*	The second one is the entry of the targe table, which is a plain table
+*	The third one is a join expression with the sourse table and target table as its parameters.
+*
+*	Each merge action of the command has its own query and plan nodes as well. And, the vars 
+*	in its target list and qual expressions may refers to the attribute in any one of the above 3 
+* 	range table entries.
+*	
+*	However, since the result tuple slots of merge actions are projected from the returned tuple of
+*	the join, we need to mapping the vars of source table and target table to their corresponding 
+*	attributes in the third range table entry. 
+*
+*	This function does the opposit of the flatten_join_alias_vars() function. It walks through the 
+*	target list and qual of a MergeAction plan, changes the vars' varno and varattno to the 
+*	corresponding position in the upper level join RTE.
+*	
+*/
+void 
+push_up_merge_action_vars(MergeAction *actplan, Query *actqry)
+{
+	push_up_merge_action_vars_context context;
+	RangeTblEntry *source_rte = rt_fetch(1,actqry->rtable);
+
+
+	/*
+	* 	We are supposed to do a  more careful assingment of the values in context
+	*	But lets take a shortcut for simple.
+	*/
+	context.varno_source = 1;
+	context.varno_target = 2;
+	context.varno_join = 3;
+
+	context.offset_source = 0;
+
+	
+	context.offset_target = list_length(source_rte->eref->colnames);
+
+	push_up_merge_action_vars_walker(actplan->plan.targetlist, &context);
+	
+	push_up_merge_action_vars_walker(actplan->plan.qual, &context);
+	
+}
+
+
+static bool
+push_up_merge_action_vars_walker(Node *node, 
+									push_up_merge_action_vars_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var *var = (Var *)node;
+
+		if(var->varno == context->varno_source)
+		{
+			var->varno = context->varno_join;
+			var->varattno += context->offset_source;
+			return false;
+		}
+		else if(var->varno == context->varno_target)
+		{
+			var->varno = context->varno_join;
+			var->varattno += context->offset_target;
+			return false;
+		}
+		else if(var->varno == context->varno_join)
+			return false;
+		else
+			elog(ERROR, "the vars in merge action tlist of qual should only belongs to the source table or targe table");
+		
+		
+	}
+	
+	return expression_tree_walker(node, push_up_merge_action_vars_walker,
+								  (void *) context);
+}
+
 
 /*
  * flatten_join_alias_vars
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..58d7a49 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
 static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
 static List *transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos);
+static Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
@@ -164,17 +165,24 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			 * Optimizable statements
 			 */
 		case T_InsertStmt:
+		case T_MergeInsert:
 			result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
 			break;
 
 		case T_DeleteStmt:
+		case T_MergeDelete:
 			result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
 			break;
 
 		case T_UpdateStmt:
+		case T_MergeUpdate:
 			result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
 			break;
 
+		case T_MergeStmt:
+			result = transformMergeStmt(pstate, (MergeStmt *)parseTree);
+			break; 
+
 		case T_SelectStmt:
 			{
 				SelectStmt *n = (SelectStmt *) parseTree;
@@ -282,22 +290,33 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	qry->commandType = CMD_DELETE;
 
-	/* set up range table with just the result rel */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_DELETE);
-
 	qry->distinctClause = NIL;
 
+	if(IsA(stmt,MergeDelete))
+		qry->isMergeAction = true;
 	/*
-	 * The USING clause is non-standard SQL syntax, and is equivalent in
-	 * functionality to the FROM list that can be specified for UPDATE. The
-	 * USING keyword is used rather than FROM because FROM is already a
-	 * keyword in the DELETE syntax.
-	 */
-	transformFromClause(pstate, stmt->usingClause);
+	* The input stmt could be a MergeDelete node. 
+	* In this case, we don't need the process on range table.
+	*/
+	if(IsA(stmt, DeleteStmt))
+	{
+		/* set up range table with just the result rel */
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_DELETE);
 
+		
+
+		/*
+		 * The USING clause is non-standard SQL syntax, and is equivalent in
+		 * functionality to the FROM list that can be specified for UPDATE. The
+		 * USING keyword is used rather than FROM because FROM is already a
+		 * keyword in the DELETE syntax.
+		 */
+		transformFromClause(pstate, stmt->usingClause);
+	}
+	
 	qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -342,11 +361,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->commandType = CMD_INSERT;
 	pstate->p_is_insert = true;
 
+	if(IsA(stmt,MergeInsert))
+		qry->isMergeAction = true;
+	
 	/*
 	 * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
 	 * VALUES list, or general SELECT input.  We special-case VALUES, both for
 	 * efficiency and so we can handle DEFAULT specifications.
 	 */
+
+	/*a MergeInsert statment is always a VALUE clause*/
 	isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
 	/*
@@ -382,7 +406,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * mentioned in the SELECT part.  Note that the target table is not added
 	 * to the joinlist or namespace.
 	 */
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
+	if(IsA(stmt,InsertStmt))/*for MergeInsert, no need to do this*/ 
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
 										 false, false, ACL_INSERT);
 
 	/* Validate stmt->cols list, or build default list if no list given */
@@ -1730,16 +1755,23 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_update = true;
 
-	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-								  interpretInhOption(stmt->relation->inhOpt),
-										 true,
-										 ACL_UPDATE);
+	if(IsA(stmt,MergeUpdate))
+		qry->isMergeAction = true;
 
-	/*
-	 * the FROM clause is non-standard SQL syntax. We used to be able to do
-	 * this with REPLACE in POSTQUEL so we keep the feature.
-	 */
-	transformFromClause(pstate, stmt->fromClause);
+	
+	if(IsA(stmt, UpdateStmt))/*for MergeUpdate, no need to do this*/
+	{
+		qry->resultRelation = setTargetTable(pstate, stmt->relation,
+									  interpretInhOption(stmt->relation->inhOpt),
+											 true,
+											 ACL_UPDATE);
+
+		/*
+		 * the FROM clause is non-standard SQL syntax. We used to be able to do
+		 * this with REPLACE in POSTQUEL so we keep the feature.
+		 */
+		transformFromClause(pstate, stmt->fromClause);
+	}
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -2241,3 +2273,304 @@ applyLockingClause(Query *qry, Index rtindex,
 	rc->pushedDown = pushedDown;
 	qry->rowMarks = lappend(qry->rowMarks, rc);
 }
+
+
+
+/*transform a action of merge command into a query. 
+No change of the pstate range table is allowed in this function. 
+*/
+static Query *
+transformMergeActions(ParseState *pstate, MergeStmt *stmt, MergeConditionAction *condact)
+{
+	Query *actqry = makeNode(Query);
+	A_Expr *match_expr; 
+	A_Expr *act_qual_expr;
+
+	/*
+	*	firstly, we need to make sure that DELETE and UPDATE 
+	*	actions are only taken in MATCHED condition, 
+	*	and INSERTs are only takend when not MATCHED
+	*/
+	if(IsA(condact->action, MergeDelete)) 
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The DELETE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{	
+		if(!condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The UPDATE action in MERGE command is not allowed when NOT MATCHED")));
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{	
+		if(condact->match) 
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 		errmsg("The INSERT action in MERGE command is not allowed when MATCHED")));
+	}
+	else
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("UNKONWN action type in MERGE")));
+
+
+		
+	act_qual_expr = condact->condition;
+	
+	/*use the transfomStmt() to parse all types of actions*/
+	if(IsA(condact->action, MergeDelete))
+	{
+		/*a delete action*/
+		MergeDelete *deleteact = (MergeDelete *)(condact->action);
+		Assert(IsA(deleteact,DeleteStmt));
+		
+		deleteact->relation = stmt->relation;
+		deleteact->usingClause = stmt->source;
+		deleteact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)deleteact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_DELETE || actqry->utilityStmt != NULL)
+			elog(ERROR, "improper DELETE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeUpdate))
+	{
+		/*an update action*/
+		MergeUpdate *updateact = (MergeUpdate *)(condact->action);
+
+		/*the "targetlist" of the updateact is filled in the parser */
+		updateact->relation = stmt->relation;
+		updateact->fromClause = stmt->source;
+		updateact->whereClause = (Node *)act_qual_expr;
+
+		/*parse the action query*/
+		actqry = transformStmt(pstate, (Node *)updateact);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_UPDATE|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper UPDATE action in merge stmt");
+
+		return actqry;
+	}
+	else if(IsA(condact->action, MergeInsert))
+	{
+		/*an insert action*/
+		Node *qual;
+		MergeInsert *insertact;
+
+		insertact = (MergeInsert *)(condact->action);
+		
+		
+		/*the "cols" and "selectStmt" of the insertact is filled in the parser */
+		insertact->relation = stmt->relation;
+
+		/*
+		the merge insert action has a strange feature. 
+		In an ordinary INSERT, the VALUES list can only contains constants and DEFAULT. (am I right??)
+		But in the INSERT action of MERGE command, the VALUES list can have expressions with variables(attributes of the targe and source tables).
+		Besides, in the ordinary INSERT, a VALUES list can never be followed by a WHERE clause. But in MERGE INSERT action, there are matching conditions. 
+
+		Thus, the output qry of this function is an INSERT query in the style of "INSERT...VALUES...", except that we have other range tables and a WHERE clause.
+		Note that it is also different from the "INSERT ... SELECT..." query, in which the whole SELECT is a subquery. (We don't have subquery here).
+		We construct this novel query structure in order to keep consitency with other merge action types (DELETE, UPDATE). 
+		In this way, all the merge action queries are in fact share the very same Range Table, They only differs in their target lists and join trees
+		
+		*/
+		
+				
+		/*parse the action query, this will call transformInsertStmt() which analyzes the VALUES list.*/
+		actqry = transformStmt(pstate, (Node *)insertact);
+
+		/*do the WHERE clause here, Since the transformInsertStmt() function 
+		only analyzes the VALUES list but not the WHERE clause*/
+		qual = transformWhereClause(pstate,(Node *)act_qual_expr, "WHERE");
+		
+		actqry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+		if(!IsA(actqry, Query) || actqry->commandType != CMD_INSERT|| actqry->utilityStmt != NULL)
+			elog(ERROR, "improper INSERT action in merge stmt");
+
+
+		return actqry;
+	}
+	else
+		elog(ERROR, "unknown action type in MERGE");
+
+	/*never comes here*/
+	return NULL;
+}
+
+
+
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
+{
+	Query	   *qry; 
+
+	ColumnRef *starRef;
+	ResTarget *starResTarget;
+	ListCell *act;
+	ListCell *l;
+	JoinExpr *joinexp;
+	int 	rtindex;
+
+	/*this will never happen, since the garm.y is restricted that only one rel name is allowed to appear in the source table position. 
+	However, if we extent the command in future, we may need to note this check here. 
+	*/
+	if(list_length(stmt->source) != 1) 
+		ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("now we only accept merge command with only ONE source table")));
+		
+	/*now, do the real tranformation of the merge command. */
+	qry = makeNode(Query);
+	qry->commandType = CMD_MERGE;
+
+	/*
+	What we are doing here is to create a query like 
+		"SELECT * FROM <source_rel> LEFT JOIN <target_rel> ON <match_condition>;"
+	Note:	
+	1. we set the "match condition" as the join qualification. 
+	The left join will scan both the matched and non-matched tuples.
+
+	2. a normal SELECT query has no "target relation". 
+	But here we need to set the targe relation in query, like the UPDATE/DELETE/INSERT queries. 
+	So this is a left join SELECT with a "target table" in its range table. 
+
+	3. We don't have a specific ACL level for Merge, here we just use ACL_SELECT. 
+	But we will add other ACL levels when handle each merge actions.  
+	*/
+
+
+	/*before analyze the FROM clause, we need to set the target table. 
+	We cannot call setTargetTable() function directly. 
+	We only need the lock target relation, without adding it to Range table. 
+	*/
+	
+	
+	setTargetTableLock(pstate, stmt->relation);
+	
+	
+	
+	/*create the FROM clause. Make the join expression first*/
+	joinexp = makeNode(JoinExpr);
+	joinexp->jointype = JOIN_LEFT;
+	joinexp->isNatural = FALSE;
+	joinexp->larg = linitial(stmt->source);/*source list has only one element*/
+	joinexp->rarg = (Node *)stmt->relation;
+	joinexp->quals = stmt->matchCondition; /*match condtion*/
+
+	/*transform the FROM clause. The target relation and source relation will be add to Rtable here.	*/
+	transformFromClause(pstate, list_make1(joinexp));
+
+	/*the targetList of the main query is "*"	  
+	*/
+	starRef = makeNode(ColumnRef);	
+	starRef->fields = list_make1(makeNode(A_Star));					
+	starRef->location = 1;					
+
+	starResTarget = makeNode(ResTarget);					
+	starResTarget->name = NULL;					
+	starResTarget->indirection = NIL;					
+	starResTarget->val = (Node *)starRef;					
+	starResTarget->location = 1;
+	
+	qry->targetList = transformTargetList(pstate, list_make1(starResTarget));
+
+	/*we don't need the WHERE clause here. Set it null. */
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+	/*now , we find out the RTE for the target relation, and do some unfinished jobs*/
+	rtindex = 1;
+	foreach(l, pstate->p_rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *)lfirst(l);
+		if(rte->relid == pstate->p_target_relation->rd_id) /*find the RTE*/
+		{
+			pstate->p_target_rangetblentry = rte;
+			rte->requiredPerms = ACL_SELECT;	
+			qry->resultRelation = rtindex;
+			break;
+		}
+		rtindex++;
+	}
+
+	if(pstate->p_target_rangetblentry == NULL)
+		elog(ERROR, "cannot find the RTE for target table");
+	
+
+	qry->rtable = pstate->p_rtable;
+
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+
+	/*
+	 * Top-level aggregates are simply disallowed in MERGE
+	 */
+	if (pstate->p_hasAggs)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("cannot use aggregate function in top level of MERGE"),
+				 parser_errposition(pstate,
+									locate_agg_of_level((Node *) qry, 0))));
+	if (pstate->p_hasWindowFuncs)
+		ereport(ERROR,
+				(errcode(ERRCODE_WINDOWING_ERROR),
+				 errmsg("cannot use window function in MERGE"),
+				 parser_errposition(pstate,
+									locate_windowfunc((Node *) qry))));
+
+
+	/*
+		the main query is done.
+	then for each actions ,we transform it to a seperate query.
+	the action queries shares the exactly same range table with the main query. 
+	in other words, in the extra condtions of the sub actions, we don't allow involvement of new tables
+	*/
+	
+	qry->mergeActQry = NIL;
+
+	foreach(act,stmt->actions)
+	{
+		MergeConditionAction *mca = lfirst(act);
+		Query *actqry;
+
+		switch(mca->action->type)
+		{
+			case T_MergeDelete:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_DELETE;
+				break;
+			case T_MergeUpdate:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_UPDATE;
+				break;
+			case T_MergeInsert:
+				pstate->p_target_rangetblentry->requiredPerms |= ACL_INSERT;				
+				break;
+			default:
+				elog(ERROR, "unknown MERGE action type %d", mca->type);
+				break;
+
+		}
+
+
+		/*transform the act (and its condition) as a single query. Link it to the top-level query*/
+		actqry = transformMergeActions(pstate, stmt, mca);
+
+		/*since we don't invoke setTargetTable() in transformMergeActions(), we need to set actqry->resultRelation here
+		*/
+		actqry->resultRelation = qry->resultRelation;
+
+		qry->mergeActQry = lappend(qry->mergeActQry, actqry);
+	}
+	
+	return qry;
+
+	
+}
+
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1722036..166ca3e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -212,6 +212,10 @@ static TypeName *TableFuncTypeName(List *columns);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 
+%type <node>	MergeStmt  opt_and_condition  merge_condition_action   merge_action
+%type <boolean> opt_not	
+%type <list> 	merge_condition_action_list
+
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
 
@@ -504,6 +508,8 @@ static TypeName *TableFuncTypeName(List *columns);
 
 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
 
+	MATCHED MERGE
+	
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
@@ -725,6 +731,7 @@ stmt :
 			| ListenStmt
 			| LoadStmt
 			| LockStmt
+			| MergeStmt 
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -6985,6 +6992,7 @@ ExplainableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
+			| MergeStmt
 			| DeclareCursorStmt
 			| CreateAsStmt
 			| ExecuteStmt					/* by default all are $$=$1 */
@@ -7330,6 +7338,101 @@ set_target_list:
 /*****************************************************************************
  *
  *		QUERY:
+ *				MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+MergeStmt: 
+			MERGE INTO relation_expr_opt_alias
+			USING  table_ref
+			ON a_expr
+			merge_condition_action_list
+				{
+					MergeStmt *m = makeNode(MergeStmt);
+
+					m->relation = $3;
+					m->source = list_make1($5);  /*although we have only one USING table, but we still make it a list, maybe in future we will allow mutliple USING tables.*/
+					m->matchCondition = $7;
+					m->actions = $8;
+
+					$$ = (Node *)m;
+				}
+				;
+	
+merge_condition_action_list: 
+							merge_condition_action 		
+								{ $$ = list_make1($1); }
+							| merge_condition_action_list merge_condition_action   
+								{ $$ = lappend($1,$2); }
+							;	
+
+merge_condition_action: 	
+							WHEN opt_not MATCHED opt_and_condition THEN merge_action
+							{
+								MergeConditionAction *m = makeNode(MergeConditionAction);
+
+								m->match = $2;
+								m->condition = $4;
+								m->action = $6;
+
+								$$ = (Node *)m;
+							}
+							;
+
+
+opt_and_condition:	
+					AND a_expr 		{$$ = $2;}
+					| /*EMPTY*/ 		{$$ = NULL;}
+					;
+
+opt_not:	
+			NOT			{$$ = false;}
+			| /*EMPTY*/	{$$ = true;}
+			;
+
+merge_action: 	
+				DELETE_P	
+					{$$ = (Node *)makeNode(MergeDelete);}
+				| UPDATE SET set_clause_list 
+					{
+						UpdateStmt *n = makeNode(MergeUpdate);
+						n->targetList = $3;
+						$$ = (Node *)n;
+					}
+				| INSERT values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = $2;
+
+						$$ = (Node *)n;
+					}
+					
+				| INSERT '(' insert_column_list ')' values_clause
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = $3;
+						n->selectStmt = $5;
+
+						$$ = (Node *)n;
+					}
+				| INSERT DEFAULT VALUES
+					{
+						InsertStmt *n = makeNode(MergeInsert);
+						n->cols = NIL;
+						n->selectStmt = NULL;
+
+						$$ = (Node *)n; 
+					}
+					
+				;	
+				
+
+
+/*****************************************************************************
+ *
+ *		QUERY:
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
@@ -10968,7 +11071,9 @@ unreserved_keyword:
 			| LOGIN_P
 			| MAPPING
 			| MATCH
+			| MATCHED
 			| MAXVALUE
+			| MERGE
 			| MINUTE_P
 			| MINVALUE
 			| MODE
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 16ca583..92b4e20 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -214,6 +214,29 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	return rtindex;
 }
 
+
+void
+setTargetTableLock(ParseState *pstate, RangeVar *relation)
+{
+	
+	/* Close old target; this could only happen for multi-action rules */
+	if (pstate->p_target_relation != NULL)
+		heap_close(pstate->p_target_relation, NoLock);
+
+	/*
+	 * Open target rel and grab suitable lock (which we will hold till end of
+	 * transaction).
+	 *
+	 * free_parsestate() will eventually do the corresponding heap_close(),
+	 * but *not* release the lock.
+	 */
+	pstate->p_target_relation = parserOpenTable(pstate, relation,
+												RowExclusiveLock);
+
+	
+}
+
+
 /*
  * Simplify InhOption (yes/no/default) into boolean yes/no.
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..9e3b30b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1837,6 +1837,45 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 }
 
 
+#define insert_rewrite (1<<0) /*insert merge action has already been processed by rewriter*/
+#define delete_rewrite (1<<1) 
+#define update_rewrite (1<<2)
+
+#define	insert_instead (1<<3) /*insert merge action is fully replace by rules.*/
+#define delete_instead (1<<4)
+#define update_instead (1<<5)
+
+
+#define merge_action_already_rewrite(acttype, flag) \
+	((acttype == CMD_INSERT && (flag & insert_rewrite)) || \
+		(acttype == CMD_UPDATE && (flag & update_rewrite)) || \
+		(acttype == CMD_DELETE && (flag & delete_rewrite)))
+
+
+#define set_action_rewrite(acttype, flag)	\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_rewrite;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_rewrite;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_rewrite;}
+
+
+				
+#define merge_action_instead(acttype, flag)		\
+			((acttype == CMD_INSERT && (flag & insert_instead)) || \
+				(acttype == CMD_UPDATE && (flag & update_instead)) || \
+				(acttype == CMD_DELETE && (flag & delete_instead)))
+
+#define set_action_instead(acttype, flag)\
+				if(acttype == CMD_INSERT)  \
+					{flag |= insert_instead;}\
+				else if(acttype == CMD_UPDATE)  \
+					{flag |= update_instead;}\
+				else if(acttype == CMD_DELETE)  \
+					{flag |= delete_instead;}
+
+
 /*
  * QueryRewrite -
  *	  Primary entry point to the query rewriter.
@@ -1861,7 +1900,149 @@ QueryRewrite(Query *parsetree)
 	 *
 	 * Apply all non-SELECT rules possibly getting 0 or many queries
 	 */
-	querylist = RewriteQuery(parsetree, NIL);
+
+	
+	if(parsetree->commandType == CMD_MERGE)
+	{
+		/*
+		*for merge query, we have a set of action queries (not subquery).
+		*each of these action queries should be applied to RewriteQuery(). 
+		*/
+		ListCell   *cell,
+				   *prev,
+				   *next;
+
+		int flag = 0;
+
+		
+		List *pre_qry = NIL;
+		List *post_qry = NIL;
+
+
+		querylist = NIL;
+		
+
+		/*rewrite the merge action queries one by one.*/
+		prev = NULL;
+
+		for (cell = list_head(parsetree->mergeActQry); cell; cell = next)
+		{
+			List *queryList4action = NIL;
+			Query *actionqry;
+			Query *q;
+
+			
+			actionqry = lfirst(cell);
+
+			next = lnext(cell);
+
+			/*if this kind of actions are fully replaced by rules, we delete it from the action list*/	
+			if(merge_action_instead(actionqry->commandType, flag))
+			{
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+				continue;
+			}
+
+
+			/*if this kind of actions are already processed by rewriter, skip it.*/
+			if(merge_action_already_rewrite(actionqry->commandType, flag))
+			{
+				/*	Still need to call RewriteQuery(), since we need the process on target list and so on. 
+					BUT, the returned list is discarded
+				*/
+				RewriteQuery(actionqry, NIL);
+			
+				prev = cell; 
+				continue;
+			}
+
+			/*ok this action has not been processed before, let's do it now.*/
+			queryList4action = RewriteQuery(actionqry, NIL);
+
+			/*this kind of actions has been processed.*/
+			set_action_rewrite(actionqry->commandType,flag); 
+
+			/*if the returning list is nil, this merge action is replaced by a do-nothing rule*/
+			if(queryList4action == NIL) 
+			{
+				/*set the flag for other merge actions of the same type*/
+				set_action_instead(actionqry->commandType, flag);
+
+				/*delete the action.*/
+				parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+
+				continue;
+			}
+
+			
+			/*the merge action query could be one of the elements in the rewriten list. 
+			*if it is in the list, it must be the head or tail.
+			*/
+			q = (Query *)linitial(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*
+				*the merge action is the head, the remaining part of the list are the queries generated by rules
+				*we put them in the post_qry list. 
+				*/
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+
+
+				queryList4action = list_delete_first(queryList4action);
+				post_qry = list_concat(post_qry,queryList4action);
+				prev = cell; 
+				continue;
+
+			}
+			
+			
+			q = (Query *)llast(queryList4action);
+			if(q->querySource == QSRC_ORIGINAL)
+			{
+				/*the merge action is the tail. Put the rule queries in pre_qry list*/
+
+				if(querylist == NIL)
+					querylist = list_make1(parsetree);
+					
+				queryList4action = list_truncate(queryList4action,list_length(queryList4action)-1);
+
+				pre_qry = list_concat(pre_qry,queryList4action);
+
+				prev = cell; 
+				continue;
+
+			}	
+				
+
+			/*here, the merge action query is not in the rewriten query list, 
+			*which means the action should be deleted.
+			*It is replaced by INSTEAD rule(s). We need to delete the action  
+
+				For a INSERT action, we put the rule queries in the post list
+				otherwise, in the pre list
+			*/
+			if(actionqry->commandType == CMD_INSERT)
+				post_qry = list_concat(post_qry,queryList4action);
+			else
+				pre_qry = list_concat(pre_qry,queryList4action);
+			
+			set_action_instead(actionqry->commandType, flag);
+			parsetree->mergeActQry = list_delete_cell(parsetree->mergeActQry, cell, prev);
+		}				
+
+		
+
+		/*finally, put the 3 lists into one. 
+		*If all the merge actions are replaced by rules, the original merge query 
+		*will not be involved in the querylist.
+		*/
+		querylist = list_concat(pre_qry,querylist);
+		querylist = list_concat(querylist, post_qry);
+			
+	}
+	else
+		querylist = RewriteQuery(parsetree, NIL);
 
 	/*
 	 * Step 2
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 8ad4915..0dc3117 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -225,6 +225,10 @@ ProcessQuery(PlannedStmt *plan,
 				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
 						 "DELETE %u", queryDesc->estate->es_processed);
 				break;
+			case CMD_MERGE:
+				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+						 "MERGE %u", queryDesc->estate->es_processed);
+				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1815539..e0dc7c3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -125,6 +125,7 @@ CommandIsReadOnly(Node *parsetree)
 			case CMD_UPDATE:
 			case CMD_INSERT:
 			case CMD_DELETE:
+			case CMD_MERGE:
 				return false;
 			default:
 				elog(WARNING, "unrecognized commandType: %d",
@@ -1405,6 +1406,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		case T_MergeStmt:
+			tag = "MERGE";
+			break;
+		
 			/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
@@ -2242,6 +2247,7 @@ GetCommandLogLevel(Node *parsetree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
+		case T_MergeStmt:
 			lev = LOGSTMT_MOD;
 			break;
 
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 67ba3e8..92f50ed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,9 @@
 #include "nodes/execnodes.h"
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
+
+extern MergeActionState *ExecInitMergeAction(MergeAction *node, EState *estate, int eflags);
+
 extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7442d2d..7f661f3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1032,9 +1032,22 @@ typedef struct ModifyTableState
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
+	List 		*mergeActPstates; /*the list of the planstate of meger command actions. 
+									NIL if this is not a merge command.
+									The elements if it are MergeActionState nodes*/
 } ModifyTableState;
 
 /* ----------------
+ *	 MergeActionState information
+ * ----------------
+ */
+typedef struct MergeActionState
+{
+	PlanState	ps;				/* its first field is NodeTag */
+	CmdType		operation;	
+} MergeActionState;
+
+/* ----------------
  *	 AppendState information
  *
  *		nplans			how many plans are in the array
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..053eee7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -44,6 +44,7 @@ typedef enum NodeTag
 	T_Plan = 100,
 	T_Result,
 	T_ModifyTable,
+	T_MergeAction,
 	T_Append,
 	T_RecursiveUnion,
 	T_BitmapAnd,
@@ -86,6 +87,7 @@ typedef enum NodeTag
 	T_PlanState = 200,
 	T_ResultState,
 	T_ModifyTableState,
+	T_MergeActionState,
 	T_AppendState,
 	T_RecursiveUnionState,
 	T_BitmapAndState,
@@ -347,6 +349,14 @@ typedef enum NodeTag
 	T_AlterUserMappingStmt,
 	T_DropUserMappingStmt,
 	T_AlterTableSpaceOptionsStmt,
+		
+	T_MergeStmt,
+	T_MergeConditionAction,
+	T_MergeUpdate, 
+	T_MergeDelete,	
+	T_MergeInsert,
+		
+		
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -511,6 +521,7 @@ typedef enum CmdType
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
 	CMD_DELETE,
+	CMD_MERGE,						/*merge stmt*/
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fec8d3c..287033f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -146,6 +146,11 @@ typedef struct Query
 
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
+
+	bool		 isMergeAction; /*if this query is a merge action. */	 
+
+	List 		*mergeActQry; /* the list of all the merge actions. 
+								* used only for merge query statment*/							 
 } Query;
 
 
@@ -990,6 +995,44 @@ typedef struct SelectStmt
 	/* Eventually add fields for CORRESPONDING spec here */
 } SelectStmt;
 
+/*The structure for MERGE command statement*/
+typedef struct MergeStmt
+{
+	NodeTag		type;
+	RangeVar   	*relation;		/*targe relation for merge */
+
+	/* source relations for the merge. 
+	*Currently, we only allwo single-source merge, 
+	*so the length of this list should always be 1 
+	*/
+	List		*source;		
+	Node	   	*matchCondition;	/* qualifications of the merge*/
+
+	/*list  of MergeConditionAction structure. 
+	*It stores all the matched / not-matched conditions and the corresponding actions
+	*The elments of this list are MergeConditionAction nodes
+	*/
+	List	   	*actions;		
+
+}MergeStmt;
+
+/*	the structure for the actions of MERGE command. 
+*	Holds info of the clauses like "... WHEN MATCHED AND ... THEN UPDATE/DELETE/INSERT"
+*/
+typedef struct MergeConditionAction
+{
+	NodeTag		type;
+	bool 		match; /*match or not match*/
+	Node		*condition;/*the AND condition for this action*/
+	Node 		*action; /*the actions: delete , insert or update*/
+}MergeConditionAction;
+
+/*
+*	The merge action nodes are in fact the ordinary nodes of UPDATE,DELETE and INSERT
+*/
+typedef UpdateStmt MergeUpdate;
+typedef DeleteStmt MergeDelete;
+typedef InsertStmt MergeInsert;
 
 /* ----------------------
  *		Set Operation node for post-analysis query trees
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 037bc0b..5a73d60 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -169,9 +169,22 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List 		*mergeActPlan;	/*the plans for merge actions, which are also ModifyTable nodes*/
 } ModifyTable;
 
 /* ----------------
+ *	 MergeAction node -
+ *		The plan node for the actions of MERGE command
+ * ----------------
+ */
+typedef struct MergeAction
+{
+	Plan plan;
+	CmdType	operation;/* INSERT, UPDATE, or DELETE */
+	List *flattenedqual; /*the flattened qual expression of action*/
+}MergeAction;
+
+/* ----------------
  *	 Append node -
  *		Generate the concatenation of the results of sub-plans.
  * ----------------
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 805dee7..6d7ff4f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -34,6 +34,7 @@ extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
 				 bool hasRecursion, double tuple_fraction,
 				 PlannerInfo **subroot);
+extern ModifyTable *merge_action_planner(PlannerGlobal *glob, Query *parse, Plan *top_plan);
 
 extern Expr *expression_planner(Expr *expr);
 
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index b0e04a0..4d6c9e8 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -15,6 +15,7 @@
 #define VAR_H
 
 #include "nodes/relation.h"
+#include "nodes/plannodes.h"
 
 typedef enum
 {
@@ -32,5 +33,5 @@ extern int	locate_var_of_relation(Node *node, int relid, int levelsup);
 extern int	find_minimum_var_level(Node *node);
 extern List *pull_var_clause(Node *node, PVCPlaceHolderBehavior behavior);
 extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
-
+extern void push_up_merge_action_vars(MergeAction * actplan,Query * actqry);
 #endif   /* VAR_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..436d459 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -229,7 +229,9 @@ PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index f3d3ee9..e4312d8 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,8 @@
 extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms);
+extern void setTargetTableLock(ParseState *pstate, RangeVar *relation);
+
 extern bool interpretInhOption(InhOption inhOpt);
 extern bool interpretOidsOption(List *defList);
 
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7529777..61c7da6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml merge
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5f185f9..0fe7006 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,4 +122,5 @@ test: returning
 test: largeobject
 test: with
 test: xml
+test: merge
 test: stats
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
new file mode 100644
index 0000000..6a85e3e
--- /dev/null
+++ b/src/test/regress/sql/merge.sql
@@ -0,0 +1,51 @@
+--
+-- testing the MERGE INTO ... command
+--
+
+-- Create the test dataset
+
+CREATE TABLE Stock (
+    item_id   INT UNIQUE,
+    balance   INT
+);
+
+INSERT INTO Stock VALUES (10, 1000);
+
+CREATE TABLE Buy (
+    item_id 	INT,
+    amount	  INT
+);
+
+INSERT INTO Buy VALUES (10, 500);
+INSERT INTO Buy VALUES (20, 2000);
+
+CREATE TABLE Sale (
+    item_id 	INT,
+    amount	  INT
+);
+
+INSERT INTO Sale VALUES (10, 1500);
+INSERT INTO Sale VALUES (20, 700);
+
+
+--Test the UPDATE + INSERT operation on stock with MERGE 
+MERGE INTO Stock USING Buy 
+			ON Stock.item_id = Buy.item_id
+ 	WHEN MATCHED THEN UPDATE SET balance = balance + Buy.amount
+ 	WHEN NOT MATCHED THEN INSERT VALUES (Buy.item_id, Buy.amount);
+
+--Look the result
+SELECT * FROM Stock ORDER BY item_id;
+
+
+-- Test the UPDATE+DELETE operation on stock with action quals
+MERGE INTO Stock USING Sale 
+			ON Stock.item_id = Sale.item_id
+   WHEN MATCHED AND balance - amount > 0 THEN UPDATE SET balance = balance - amount
+   WHEN MATCHED THEN DELETE;
+   
+   
+SELECT * FROM STOCK ORDER BY item_id;
+ 
+--clean up
+DROP TABLE Stock, Buy, Sale;
#19Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Boxuan Zhai (#17)
Re: merge command - GSoC progress

On 04/08/10 12:23, Boxuan Zhai wrote:

I am just considering that there may be some logical mistakes for my rule
rewriting strategy of MERGE actions.

In my current design, if we find that an action type, say UPDATE, is
replaced by INSTEAD rules, we will remove all the actions of this type from
the MERGE command, as if they are not be specified by user from the
beginning. See the test example in my pages for this situation.
https://wiki.postgresql.org/wiki/MergeTestExamples#With_INSTEAD_rules

Now,I am thinking that maybe we should keep the replaced actions in action
list, and just mark them to be "invalid". If one join tuple from the main
plan fits the condition of this action, we will do nothing on it.

This strategy is a little bit different with the current one. If we delete
an action, the tuples that meet it condition will be caught by other
actions. If we keep it, the tuples that match it will be skipped.

I think the new design is more logical, and I wonder your opinion on this
problem.

So if I understood correctly, in the instead rule example you have at
the wiki page, the stock table should contain one row, with the same
balance it had before running the MERGE? Yeah, agreed, that's much more
logical.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#20Andres Freund
andres@anarazel.de
In reply to: Heikki Linnakangas (#18)
Re: merge command - GSoC progress

On Wednesday 04 August 2010 14:09:51 Heikki Linnakangas wrote:

Yep. I believe Boxuan is using git in a simplistic way, doing just "git
diff" to create patches. For adding new files, you need to do "git add
<filename>", but note that this adds the new file to "staging area". To
view all changes in the staging area, use "git diff --cached", but that
won't show any modifications to existing files that you haven't also
"git add"ed. So to generate a patch you need to "git add" all modified
and added files ("git add -u" will add all modified files
automatically), and then use "git diff --cached" to generate the diff.

Or use git add --intent--to-add (or -N). That adds the file but not the actual
changes.

Andres

#21Simon Riggs
simon@2ndQuadrant.com
In reply to: Boxuan Zhai (#17)
Re: merge command - GSoC progress

On Wed, 2010-08-04 at 17:23 +0800, Boxuan Zhai wrote:

Dear Robert,

I am just considering that there may be some logical mistakes for my
rule rewriting strategy of MERGE actions.

In my current design, if we find that an action type, say UPDATE, is
replaced by INSTEAD rules, we will remove all the actions of this type
from the MERGE command, as if they are not be specified by user from
the beginning. See the test example in my pages for this situation.
https://wiki.postgresql.org/wiki/MergeTestExamples#With_INSTEAD_rules

It seems sensible to use the test files that I wrote for MERGE in 2008,
published to -hackers at that time.

The tests were a complete output from a MERGE test script.

Developing new tests when we already have code makes little sense, plus
its a good way of objectively testing that the spec has been implemented
correctly in these patches.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services

#22Simon Riggs
simon@2ndQuadrant.com
In reply to: Simon Riggs (#21)
1 attachment(s)
Re: merge command - GSoC progress

On Wed, 2010-08-04 at 15:36 +0100, Simon Riggs wrote:

On Wed, 2010-08-04 at 17:23 +0800, Boxuan Zhai wrote:

Dear Robert,

I am just considering that there may be some logical mistakes for my
rule rewriting strategy of MERGE actions.

In my current design, if we find that an action type, say UPDATE, is
replaced by INSTEAD rules, we will remove all the actions of this type
from the MERGE command, as if they are not be specified by user from
the beginning. See the test example in my pages for this situation.
https://wiki.postgresql.org/wiki/MergeTestExamples#With_INSTEAD_rules

It seems sensible to use the test files that I wrote for MERGE in 2008,
published to -hackers at that time.

Even more sensible for me to include it as a patch, with the files in
the right places and the schedules updated.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Training and Services

Attachments:

merge_tests.patchtext/x-patch; charset=UTF-8; name=merge_tests.patchDownload
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
new file mode 100644
index 0000000..18e3891
--- /dev/null
+++ b/src/test/regress/expected/merge.out
@@ -0,0 +1,279 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      20
+  3 |      30
+(3 rows)
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+ id | balance 
+----+---------
+  2 |       5
+  3 |      20
+  4 |      40
+(3 rows)
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      25
+  3 |      50
+(3 rows)
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      20
+  3 |      30
+  4 |      40
+(4 rows)
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      25
+  3 |      50
+  4 |      40
+(4 rows)
+
+ROLLBACK;
+--
+-- Non-standard functionality
+-- 
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	DELETE
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+(1 row)
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  3 |      50
+  4 |      40
+(3 rows)
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+ id | balance 
+----+---------
+  2 |       5
+  3 |       5
+  3 |      20
+  4 |      40
+(4 rows)
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+--  fails according to standard
+--  fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+ERROR:  multiple actions on single target row
+ 
+ROLLBACK;
+
+-- This next SQL statement
+--  fails according to standard
+--  suceeds in PostgreSQL implementation by simply ignoring the second
+--  matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+ id | balance 
+----+---------
+  2 |       5
+  3 |      20
+  4 |       5
+  4 |      40
+(4 rows)
+
+-- This next SQL statement
+--  suceeds according to standard (yes, it is inconsistent)
+--  suceeds in PostgreSQL implementation, though could easily fail if
+--  there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      20
+  3 |      30
+  4 |       5
+  4 |      40
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      20
+  3 |      30
+    |
+    |
+(5 rows)
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+	INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+	DO NOTHING
+;
+SELECT * FROM target;
+ id | balance 
+----+---------
+  1 |      10
+  2 |      20
+  3 |      30
+(3 rows)
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+	INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+	UPDATE SET balance = t.balance + s.balance
+;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7529777..61c7da6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml merge
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5f185f9..0fe7006 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,4 +122,5 @@ test: returning
 test: largeobject
 test: with
 test: xml
+test: merge
 test: stats
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
new file mode 100644
index 0000000..7ecd02e
--- /dev/null
+++ b/src/test/regress/sql/merge.sql
@@ -0,0 +1,200 @@
+--
+-- MERGE
+--
+CREATE TABLE target (id integer, balance integer);
+CREATE TABLE source (id integer, balance integer);
+INSERT INTO target VALUES (1, 10);
+INSERT INTO target VALUES (2, 20);
+INSERT INTO target VALUES (3, 30);
+SELECT * FROM target;
+
+--
+-- initial tests
+--
+-- empty source means 0 rows touched
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+;
+-- insert some source rows to work from
+INSERT INTO source VALUES (2, 5);
+INSERT INTO source VALUES (3, 20);
+INSERT INTO source VALUES (4, 40);
+SELECT * FROM source;
+
+-- do a simple equivalent of an UPDATE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+;
+
+ROLLBACK;
+-- do a simple equivalent of an INSERT SELECT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Non-standard functionality
+-- 
+-- do a simple equivalent of a DELETE join
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED THEN
+	DELETE
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- now the classic UPSERT, with a DELETE
+-- the Standard doesn't allow the DELETE clause for some reason,
+-- though other implementations do
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- Prepare the test data to generate multiple matching rows for a single target
+INSERT INTO source VALUES (3, 5);
+SELECT * FROM source ORDER BY id, balance;
+
+-- we now have a duplicate key in source, so when we join to
+-- target we will generate 2 matching rows, not one
+-- In the following statement row id=3 will be both updated
+-- and deleted by this statement and so will cause a run-time error
+-- when the second change to that row is detected
+-- This next SQL statement
+--  fails according to standard
+--  fails in PostgreSQL implementation
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+ERROR:  multiple actions on single target row
+ 
+ROLLBACK;
+
+-- This next SQL statement
+--  fails according to standard
+--  suceeds in PostgreSQL implementation by simply ignoring the second
+--  matching row since it activates no WHEN clause
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 10 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+ROLLBACK;
+-- Now lets prepare the test data to generate 2 non-matching rows
+DELETE FROM source WHERE id = 3 AND balance = 5;
+INSERT INTO source VALUES (4, 5);
+SELECT * FROM source;
+
+-- This next SQL statement
+--  suceeds according to standard (yes, it is inconsistent)
+--  suceeds in PostgreSQL implementation, though could easily fail if
+--  there was an appropriate unique constraint
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement works, but since there is no WHEN clause that
+-- applies to non-matching rows, SQL standard requires us to generate
+-- rows with DEFAULT VALUES for all columns, which is why we support the
+-- syntax DO NOTHING (similar to the way Rules work) in addition
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+	INSERT VALUES (s.id, s.balance)
+;
+SELECT * FROM target;
+
+ROLLBACK;
+-- This next SQL statement suceeds, but does nothing since there are
+-- only non-matching rows that do not activate a WHEN clause, so we
+-- provide syntax to just ignore them, rather than allowing data quality
+-- problems
+BEGIN;
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN NOT MATCHED AND s.balance > 100 THEN
+	INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED
+	DO NOTHING
+;
+SELECT * FROM target;
+
+ROLLBACK;
+--
+-- Weirdness
+--
+-- MERGE statement containing WHEN clauses that are never executable
+-- NOT an error under the standard
+MERGE into target t
+USING (select * from source) AS s
+ON t.id = s.id
+WHEN MATCHED AND s.balance > 0 THEN
+	UPDATE SET balance = t.balance + s.balance
+WHEN MATCHED THEN
+	DELETE
+WHEN NOT MATCHED THEN
+	INSERT VALUES (s.id, s.balance)
+WHEN NOT MATCHED THEN /* never executed because of order of WHEN clauses */
+	INSERT VALUES (s.id, s.balance + 10)
+WHEN MATCHED THEN /* never executed because of order of WHEN clauses */
+	UPDATE SET balance = t.balance + s.balance
+;