On Sat, Jul 10, 2010 at 01:18:49PM -0400, Tom Lane wrote:
> Robert Haas <[email protected]> writes:
> > Though, if the worst problem with this patch is the formatting, we're doing
> > *quite* well.
>
> Well, the worst problem with it is that it hasn't touched the
> interesting part, ie, what happens at execution time. I haven't
> seen a design for that, which means it's impossible to evaluate
> whether the code that is here is of any use. We might need some
> other representation entirely.
>
> BTW, Fetter's version of the patch seems to be lacking any gram.y
> changes, but surely those exist already?
Oops.
Fixed that now in attached patch.
Cheers,
David.
--
David Fetter <[email protected]> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: [email protected]
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/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..46ccdbb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -200,7 +200,7 @@ static TypeName *TableFuncTypeName(List *columns);
DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
- LockStmt NotifyStmt ExplainableStmt PreparableStmt
+ LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt
RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -233,6 +233,7 @@ static TypeName *TableFuncTypeName(List *columns);
%type <boolean> opt_force opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
+ opt_not
%type <list> OptRoleList AlterOptRoleList
%type <defelt> CreateOptRoleElem AlterOptRoleElem
@@ -301,6 +302,9 @@ static TypeName *TableFuncTypeName(List *columns);
opt_enum_val_list enum_val_list
table_func_column_list
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
+ merge_condition_action_list
+
+%type <node> opt_and_condition merge_condition_action merge_action
%type <range> OptTempTableName
%type <into> into_clause create_as_target
@@ -502,7 +506,7 @@ static TypeName *TableFuncTypeName(List *columns);
LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
LOCATION LOCK_P LOGIN_P
- MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MAPPING MATCH MATCHED MAXVALUE MERGE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
@@ -725,6 +729,7 @@ stmt :
| ListenStmt
| LoadStmt
| LockStmt
+ | MergeStmt
| NotifyStmt
| PrepareStmt
| ReassignOwnedStmt
@@ -7085,6 +7090,102 @@ DeallocateStmt: DEALLOCATE name
}
;
+
+
+/*****************************************************************************
+ *
+ * QUERY:
+ * MERGE STATEMENT
+ *
+ *****************************************************************************/
+
+
+
+MergeStmt:
+ MERGE INTO relation_expr_opt_alias
+ USING table_ref
+ ON a_expr
+ merge_condition_action_list
+ {
+ MergeStmt *m = makeNode(MergeStmt);
+
+ m->relation = $3;
+ m->source = list_make1($5); /*although
we have only one USING table, but we still make it a list, maybe in future we
will allow mutliple USING tables.*/
+ m->matchCondition = $7;
+ m->actions = $8;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+merge_condition_action_list:
+ merge_condition_action
+ { $$ =
list_make1($1); }
+ |
merge_condition_action_list merge_condition_action
+ { $$ =
lappend($1,$2); }
+ ;
+
+merge_condition_action:
+ WHEN opt_not MATCHED
opt_and_condition THEN merge_action
+ {
+
MergeConditionAction *m = makeNode(MergeConditionAction);
+
+ m->match = $2;
+ m->condition =
$4;
+ m->action = $6;
+
+ $$ = (Node *)m;
+ }
+ ;
+
+
+opt_and_condition:
+ AND a_expr {$$ = $2;}
+ | /*EMPTY*/ {$$ = NULL;}
+ ;
+
+opt_not:
+ NOT {$$ = false;}
+ | /*EMPTY*/ {$$ = true;}
+ ;
+
+merge_action:
+ DELETE_P
+ {$$ = (Node *)makeNode(MergeDelete);}
+ | UPDATE SET set_clause_list
+ {
+ UpdateStmt *n =
makeNode(MergeUpdate);
+ n->targetList = $3;
+ $$ = (Node *)n;
+ }
+ | INSERT values_clause
+ {
+ InsertStmt *n =
makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = $2;
+
+ $$ = (Node *)n;
+ }
+
+ | INSERT '(' insert_column_list ')'
values_clause
+ {
+ InsertStmt *n =
makeNode(MergeInsert);
+ n->cols = $3;
+ n->selectStmt = $5;
+
+ $$ = (Node *)n;
+ }
+ | INSERT DEFAULT VALUES
+ {
+ InsertStmt *n =
makeNode(MergeInsert);
+ n->cols = NIL;
+ n->selectStmt = NULL;
+
+ $$ = (Node *)n;
+ }
+
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -7207,7 +7308,6 @@ opt_nowait: NOWAIT
{ $$ = TRUE; }
| /*EMPTY*/
{ $$ = FALSE; }
;
-
/*****************************************************************************
*
* QUERY:
@@ -7215,7 +7315,7 @@ opt_nowait: NOWAIT
{ $$ = TRUE; }
*
*****************************************************************************/
-UpdateStmt: UPDATE relation_expr_opt_alias
+UpdateStmt: UPDATE relation_expr_opt_alias
SET set_clause_list
from_clause
where_or_current_clause
@@ -7828,6 +7928,7 @@ values_clause:
}
;
+
/*****************************************************************************
*
@@ -10935,7 +11036,9 @@ unreserved_keyword:
| LOGIN_P
| MAPPING
| MATCH
+ | MATCHED
| MAXVALUE
+ | MERGE
| MINUTE_P
| MINVALUE
| MODE
diff --git a/src/backend/parser/parse_clause.c
b/src/backend/parser/parse_clause.c
index f30132a..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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers