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 LEFT JOIN ON ;" + * + * 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 opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option opt_nowait opt_if_exists opt_with_data + opt_not %type OptRoleList AlterOptRoleList %type 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 opt_and_condition merge_condition_action merge_action %type OptTempTableName %type 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);