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.
> 
> Thanks,
> Andres

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.
-- 
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..aad914c 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..357de31 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..ae9236a 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,17 +1748,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, 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);
 
        qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
@@ -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,326 @@ 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;
+
+
+       //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;
+
+//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..cf24985 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..90c4256 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1861,7 +1861,57 @@ 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..0a619d2 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..05e98d9 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;              /*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;
+
+
+
+
 /*****************************************************************************
  *             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