On Sat, Jul 10, 2010 at 09:26:38AM -0700, David Fetter wrote:
> On Sat, Jul 10, 2010 at 11:52:31AM +0200, Andres Freund wrote:
> > On Fri, Jul 09, 2010 at 11:33:04PM -0400, Robert Haas wrote:
> > > On Fri, Jul 9, 2010 at 10:25 PM, Boxuan Zhai <bxzhai2...@gmail.com> wrote:
> > > > Dear All,
> > > >
> > > > This is ZHAI BOXUAN, a student of gSoC 2010. My project is to add merge
> > > > command in postgres.
> > > > There is a more detailed instruction in readme.
> > I would find it helpfull to find a short recap of how you want to
> > handle the various problems (mostly around locking) in the readme.
> > 
> > > > Any comments will be highly appreciated.
> > > Is there any chance you can submit this as a single patch file?  Or if
> > > not, can you at least use a zip or tar file instead of a RAR archive?
> > > Ideally the patch would be against CVS HEAD, not 8.4.3.
> > 
> > I would also suggest you base your patch either against the git tree
> > or CVS. Currently it does include patches agains generated files like
> > gram.y or kwlist.h which make it harder to see the real changes.
> > 
> Please find enclosed a patch against git master as of
> 7b2668159bb4d0f5177a23d05bf7c2ab00bc0d75.  It works up to make, but
> fails on make check.
> 
> I'm thinking the docs for INSERT, UPDATE, and DELETE should link to
> the docs for this, as they get written.
> 
> Cheers,
> David.

Oops.  Just noticed that there were 56 lines' worth of C++ style
comments, which I've corrected in the enclosed patch, along with some
spelling mistakes, grammar, and gratuitous white space.

Cheers,
David.
-- 
David Fetter <da...@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fet...@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
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e770e89..f72ebcf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2231,6 +2231,7 @@ _copyQuery(Query *from)
        COPY_SCALAR_FIELD(querySource);
        COPY_SCALAR_FIELD(canSetTag);
        COPY_NODE_FIELD(utilityStmt);
+       COPY_NODE_FIELD(mergeActQry); /* merge actions */
        COPY_SCALAR_FIELD(resultRelation);
        COPY_NODE_FIELD(intoClause);
        COPY_SCALAR_FIELD(hasAggs);
@@ -2324,6 +2325,36 @@ _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 SetOperationStmt *
 _copySetOperationStmt(SetOperationStmt *from)
 {
@@ -4148,7 +4179,14 @@ copyObject(void *from)
                case T_AlterTSConfigurationStmt:
                        retval = _copyAlterTSConfigurationStmt(from);
                        break;
+               case T_MergeStmt:
+                       retval = _copyMergeStmt(from);
+                       break;
 
+
+               case T_MergeConditionAction:
+                       retval = _copyMergeConditionAction(from);
+                       break;
                case T_A_Expr:
                        retval = _copyAExpr(from);
                        break;
@@ -4244,7 +4282,7 @@ copyObject(void *from)
                        break;
 
                default:
-                       elog(ERROR, "unrecognized node type: %d", (int) 
nodeTag(from));
+                       elog(ERROR, "unrecognized node type: %d in copyObject() 
function", (int) nodeTag(from));
                        retval = from;          /* keep compiler quiet */
                        break;
        }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5d83727..8ab3247 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -855,6 +855,7 @@ _equalQuery(Query *a, Query *b)
        COMPARE_SCALAR_FIELD(querySource);
        COMPARE_SCALAR_FIELD(canSetTag);
        COMPARE_NODE_FIELD(utilityStmt);
+       COMPARE_NODE_FIELD(mergeActQry);
        COMPARE_SCALAR_FIELD(resultRelation);
        COMPARE_NODE_FIELD(intoClause);
        COMPARE_SCALAR_FIELD(hasAggs);
@@ -2933,7 +2934,7 @@ equal(void *a, void *b)
                        break;
 
                default:
-                       elog(ERROR, "unrecognized node type: %d",
+                       elog(ERROR, "unrecognized node type: %d in equal() 
function",
                                 (int) nodeTag(a));
                        retval = false;         /* keep compiler quiet */
                        break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e7dae4b..b65dc58 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1984,6 +1984,7 @@ _outQuery(StringInfo str, Query *node)
        else
                appendStringInfo(str, " :utilityStmt <>");
 
+       WRITE_NODE_FIELD(mergeActQry);
        WRITE_INT_FIELD(resultRelation);
        WRITE_NODE_FIELD(intoClause);
        WRITE_BOOL_FIELD(hasAggs);
@@ -2439,6 +2440,46 @@ _outConstraint(StringInfo str, Constraint *node)
 }
 
 
+
+
+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);
+
+
+}
+
 /*
  * _outNode -
  *       converts a Node into ascii string and append it to 'str'
@@ -2889,6 +2930,16 @@ _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/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..7862212 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -64,6 +64,8 @@ static Query *transformExplainStmt(ParseState *pstate,
 static void transformLockingClause(ParseState *pstate, Query *qry,
                                           LockingClause *lc, bool pushedDown);
 
+static Query *
+transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
 
 /*
  * parse_analyze
@@ -164,14 +166,17 @@ 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;
 
@@ -188,6 +193,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
                        }
                        break;
 
+               case T_MergeStmt:
+                       result = transformMergeStmt(pstate, (MergeStmt 
*)parseTree);
+                       break;
+
                        /*
                         * Special cases
                         */
@@ -282,12 +291,14 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
        qry->commandType = CMD_DELETE;
 
-       /* set up range table with just the result rel */
-       qry->resultRelation = setTargetTable(pstate, stmt->relation,
+       if(IsA(stmt,DeleteStmt)) /* For MergeDelete, no need to do this. */
+       {
+               /* 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;
 
        /*
@@ -296,7 +307,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
         * USING keyword is used rather than FROM because FROM is already a
         * keyword in the DELETE syntax.
         */
-       transformFromClause(pstate, stmt->usingClause);
+       if(IsA(stmt,DeleteStmt)) /* For MergeDelete, no need to do this. */
+               transformFromClause(pstate, stmt->usingClause);
 
        qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
 
@@ -347,6 +359,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
         * 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 VALUE clause */
+
        isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
 
        /*
@@ -382,7 +397,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 */
@@ -695,7 +711,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                                 errmsg("cannot use window function in VALUES"),
                                 parser_errposition(pstate,
                                                                        
locate_windowfunc((Node *) qry))));
-
        return qry;
 }
 
@@ -892,6 +907,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
                                                           (LockingClause *) 
lfirst(l), false);
        }
 
+
+/* printf("%s\n", nodeToString(qry)); */
+
        return qry;
 }
 
@@ -1730,16 +1748,22 @@ 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, UpdateStmt))/* for MergeUpdate, no need to do this */
+       {
+               /* for a MergeUpdate node, we have no need to se the target and 
source rels */
+               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);
+       }
+
 
-       /*
-        * 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);
 
@@ -1806,12 +1830,14 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt 
*stmt)
                origTarget = (ResTarget *) lfirst(origTargetList);
                Assert(IsA(origTarget, ResTarget));
 
+printf("targe entry :%s\n", nodeToString(origTarget));         
+
                attrno = attnameAttNum(pstate->p_target_relation,
                                                           origTarget->name, 
true);
                if (attrno == InvalidAttrNumber)
                        ereport(ERROR,
                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                        errmsg("column \"%s\" of relation 
\"%s\" does not exist",
+                                        errmsg("column \"%s\" of relation 
\"%s\" does not exist in transformUpdateStmt()",
                                                        origTarget->name,
                                                 
RelationGetRelationName(pstate->p_target_relation)),
                                         parser_errposition(pstate, 
origTarget->location)));
@@ -2241,3 +2267,370 @@ 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 condition 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;
+
+
+       /* 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 separate 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;
+
+/* printf("finish one action qry: \n%s\n", nodeToString(actqry)); */
+
+               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;
+       }
+
+/* printf("the content of main qry is : \n%s\n----\n-----", 
nodeToString(qry)); */
+       return qry;
+
+}
+
diff --git a/src/backend/parser/parse_clause.c 
b/src/backend/parser/parse_clause.c
index f30132a..33855b1 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -213,6 +213,35 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
        return rtindex;
 }
 
+
+
+/*
+ * setTargetTableLock
+ *     only set the lock for targe table, without adding it to range table
+ */
+
+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/parser/parse_relation.c 
b/src/backend/parser/parse_relation.c
index 38c7e91..b2fcd1c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1993,7 +1993,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber 
attnum,
                                if (att_tup->attisdropped)
                                        ereport(ERROR,
                                                        
(errcode(ERRCODE_UNDEFINED_COLUMN),
-                                       errmsg("column \"%s\" of relation 
\"%s\" does not exist",
+                                       errmsg("column \"%s\" of relation 
\"%s\" does not exist when get the attr type",
                                                   NameStr(att_tup->attname),
                                                   get_rel_name(rte->relid))));
                                *vartype = att_tup->atttypid;
@@ -2035,7 +2035,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber 
attnum,
                                        if (attnum < 1 || attnum > 
tupdesc->natts)
                                                ereport(ERROR,
                                                                
(errcode(ERRCODE_UNDEFINED_COLUMN),
-                                               errmsg("column %d of relation 
\"%s\" does not exist",
+                                               errmsg("column %d of relation 
\"%s\" does not exist when get the attr type1",
                                                           attnum,
                                                           
rte->eref->aliasname)));
 
@@ -2048,7 +2048,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber 
attnum,
                                        if (att_tup->attisdropped)
                                                ereport(ERROR,
                                                                
(errcode(ERRCODE_UNDEFINED_COLUMN),
-                                                                errmsg("column 
\"%s\" of relation \"%s\" does not exist",
+                                                                errmsg("column 
\"%s\" of relation \"%s\" does not exist when get the attr type2",
                                                                                
NameStr(att_tup->attname),
                                                                                
rte->eref->aliasname)));
                                        *vartype = att_tup->atttypid;
diff --git a/src/backend/parser/parse_target.c 
b/src/backend/parser/parse_target.c
index e542dc0..282ad8f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -805,7 +805,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List 
**attrnos)
                        if (attrno == InvalidAttrNumber)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_UNDEFINED_COLUMN),
-                                       errmsg("column \"%s\" of relation 
\"%s\" does not exist",
+                                       errmsg("column \"%s\" of relation 
\"%s\" does not exist in checkInsertTargets()",
                                                   name,
                                                 
RelationGetRelationName(pstate->p_target_relation)),
                                                 parser_errposition(pstate, 
col->location)));
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index 8d0932b..bd1f9f9 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -114,7 +114,7 @@ LookupTypeName(ParseState *pstate, const TypeName *typeName,
                if (attnum == InvalidAttrNumber)
                        ereport(ERROR,
                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                        errmsg("column \"%s\" of relation 
\"%s\" does not exist",
+                                        errmsg("column \"%s\" of relation 
\"%s\" does not exist in LookupTypeName",
                                                        field, rel->relname),
                                         parser_errposition(pstate, 
typeName->location)));
                typoid = get_atttype(relid, attnum);
diff --git a/src/backend/rewrite/rewriteHandler.c 
b/src/backend/rewrite/rewriteHandler.c
index 25b44dd..c863b20 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1861,7 +1861,58 @@ 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().  And, in all cases, the
+                * original query should be excuted.  However, I am not sure
+                * how to run the rules for a merge command yet. :(
+                */
+
+               ListCell *l;
+
+               querylist = NIL;
+
+               foreach(l, parsetree->mergeActQry)
+               {
+                       List *queryList4action;
+                       Query  *q;
+
+                       queryList4action = RewriteQuery((Query *)lfirst(l), 
NIL);
+
+                       if(queryList4action == NIL)
+                               continue;
+
+                       /* We need to remove the orignal query from the list.  
If
+                        * it is in the list, it must be either the head or the
+                        * tail.
+                        */
+
+                       q = (Query *)linitial(queryList4action);
+                       if(q->querySource == QSRC_ORIGINAL)
+                               queryList4action = 
list_delete_first(queryList4action);
+                       else
+                       {
+                               q = (Query *)llast(queryList4action);
+                               if(q->querySource == QSRC_ORIGINAL)
+                                       queryList4action = 
list_truncate(queryList4action,list_length(queryList4action)-1);
+                       }       
+
+
+                       /* Append the rule queries of this action to the full 
querylist */
+                       querylist = list_concat(querylist,queryList4action);
+               }
+
+               /* Finally, put the original query at the head. */
+               querylist = lcons(parsetree,querylist);
+
+
+       }
+       else /* a plain query */
+               querylist = RewriteQuery(parsetree, NIL);
 
        /*
         * Step 2
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8960246..846c7c4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1398,6 +1398,10 @@ CreateCommandTag(Node *parsetree)
                        tag = "SELECT";
                        break;
 
+               case T_MergeStmt:
+                       tag = "MERGE";
+                       break;
+
                        /* utility statements --- same whether raw or cooked */
                case T_TransactionStmt:
                        {
@@ -2206,7 +2210,7 @@ CreateCommandTag(Node *parsetree)
                        break;
 
                default:
-                       elog(WARNING, "unrecognized node type: %d",
+                       elog(WARNING, "unrecognized node type: %d for command 
tag creation",
                                 (int) nodeTag(parsetree));
                        tag = "???";
                        break;
@@ -2235,6 +2239,7 @@ GetCommandLogLevel(Node *parsetree)
                case T_InsertStmt:
                case T_DeleteStmt:
                case T_UpdateStmt:
+               case T_MergeStmt:
                        lev = LOGSTMT_MOD;
                        break;
 
@@ -2659,7 +2664,7 @@ GetCommandLogLevel(Node *parsetree)
                        break;
 
                default:
-                       elog(WARNING, "unrecognized node type: %d",
+                       elog(WARNING, "unrecognized node type: %d when get the 
command log level",
                                 (int) nodeTag(parsetree));
                        lev = LOGSTMT_ALL;
                        break;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 1b5e476..634ca60 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -260,6 +260,14 @@ 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,
@@ -509,7 +517,9 @@ typedef enum CmdType
        CMD_SELECT,                                     /* select stmt */
        CMD_UPDATE,                                     /* update stmt */
        CMD_INSERT,                                     /* insert stmt */
-       CMD_DELETE,
+       CMD_DELETE,                                     /*delete stmt*/
+       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..e69b497 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -108,6 +108,9 @@ typedef struct Query
        Node       *utilityStmt;        /* non-null if this is DECLARE CURSOR 
or a
                                                                 * 
non-optimizable statement */
 
+       List            *mergeActQry; /*  the list of all the merge actions. 
used only for merge query statment */
+
+
        int                     resultRelation; /* rtable index of target 
relation for
                                                                 * 
INSERT/UPDATE/DELETE; 0 for SELECT */
 
@@ -922,6 +925,9 @@ typedef struct UpdateStmt
        List       *returningList;      /* list of expressions to return */
 } UpdateStmt;
 
+
+
+
 /* ----------------------
  *             Select Statement
  *
@@ -1023,6 +1029,33 @@ typedef struct SetOperationStmt
 } SetOperationStmt;
 
 
+/* ZBX: the structure for MERGE command statement */
+typedef struct MergeStmt
+{
+       NodeTag         type;
+       RangeVar        *relation;              /* target 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;
+
+
+
+
 /*****************************************************************************
  *             Other Statements (no optimizations required)
  *
diff --git a/src/include/parser/parse_clause.h 
b/src/include/parser/parse_clause.h
index f3d3ee9..2541f50 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -19,6 +19,9 @@
 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);
 
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to