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