diff options
Diffstat (limited to 'src/backend')
26 files changed, 416 insertions, 116 deletions
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 056b6733c8e..28cf8b040ab 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -281,12 +281,6 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, { Assert(stmt->query); - /* MERGE is allowed by parser, but unimplemented. Reject for now */ - if (IsA(stmt->query, MergeStmt)) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MERGE not supported in COPY")); - query = makeNode(RawStmt); query->stmt = stmt->query; query->stmt_location = stmt_location; diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index a6962e0cb77..ae8b2e36d72 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -503,7 +503,8 @@ BeginCopyTo(ParseState *pstate, { Assert(query->commandType == CMD_INSERT || query->commandType == CMD_UPDATE || - query->commandType == CMD_DELETE); + query->commandType == CMD_DELETE || + query->commandType == CMD_MERGE); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index ffd3ca4e619..728c8d5fda9 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1107,6 +1107,19 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_MergeSupportFunc: + { + /* must be in a MERGE, else something messed up */ + if (!state->parent || + !IsA(state->parent, ModifyTableState) || + ((ModifyTableState *) state->parent)->operation != CMD_MERGE) + elog(ERROR, "MergeSupportFunc found in non-merge plan node"); + + scratch.opcode = EEOP_MERGE_SUPPORT_FUNC; + ExprEvalPushStep(state, &scratch); + break; + } + case T_SubscriptingRef: { SubscriptingRef *sbsref = (SubscriptingRef *) node; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 7c1f51e2e0a..a25ab7570fe 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -484,6 +484,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_AGGREF, &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, + &&CASE_EEOP_MERGE_SUPPORT_FUNC, &&CASE_EEOP_SUBPLAN, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, @@ -1592,6 +1593,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_MERGE_SUPPORT_FUNC) + { + /* too complex/uncommon for an inline implementation */ + ExecEvalMergeSupportFunc(state, op, econtext); + + EEO_NEXT(); + } + EEO_CASE(EEOP_SUBPLAN) { /* too complex for an inline implementation */ @@ -4246,6 +4255,45 @@ ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op) } /* + * ExecEvalMergeSupportFunc + * + * Returns information about the current MERGE action for its RETURNING list. + */ +void +ExecEvalMergeSupportFunc(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + ModifyTableState *mtstate = castNode(ModifyTableState, state->parent); + MergeActionState *relaction = mtstate->mt_merge_action; + + if (!relaction) + elog(ERROR, "no merge action in progress"); + + /* Return the MERGE action ("INSERT", "UPDATE", or "DELETE") */ + switch (relaction->mas_action->commandType) + { + case CMD_INSERT: + *op->resvalue = PointerGetDatum(cstring_to_text_with_len("INSERT", 6)); + *op->resnull = false; + break; + case CMD_UPDATE: + *op->resvalue = PointerGetDatum(cstring_to_text_with_len("UPDATE", 6)); + *op->resnull = false; + break; + case CMD_DELETE: + *op->resvalue = PointerGetDatum(cstring_to_text_with_len("DELETE", 6)); + *op->resnull = false; + break; + case CMD_NOTHING: + elog(ERROR, "unexpected merge action: DO NOTHING"); + break; + default: + elog(ERROR, "unrecognized commandType: %d", + (int) relaction->mas_action->commandType); + } +} + +/* * Hand off evaluation of a subplan to nodeSubplan.c */ void diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 8ca512db186..64fcb012dbe 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -609,8 +609,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, * Build the RETURNING projection for the partition. Note that we didn't * build the returningList for partitions within the planner, but simple * translation of varattnos will suffice. This only occurs for the INSERT - * case or in the case of UPDATE tuple routing where we didn't find a - * result rel to reuse. + * case or in the case of UPDATE/MERGE tuple routing where we didn't find + * a result rel to reuse. */ if (node && node->returningLists != NIL) { @@ -619,12 +619,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, List *returningList; /* See the comment above for WCO lists. */ - /* (except no RETURNING support for MERGE yet) */ Assert((node->operation == CMD_INSERT && list_length(node->returningLists) == 1 && list_length(node->resultRelations) == 1) || (node->operation == CMD_UPDATE && list_length(node->returningLists) == + list_length(node->resultRelations)) || + (node->operation == CMD_MERGE && + list_length(node->returningLists) == list_length(node->resultRelations))); /* diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 6e926ef4eed..539cd0a9991 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1662,8 +1662,8 @@ check_sql_fn_retval(List *queryTreeLists, /* * If it's a plain SELECT, it returns whatever the targetlist says. - * Otherwise, if it's INSERT/UPDATE/DELETE with RETURNING, it returns - * that. Otherwise, the function return type must be VOID. + * Otherwise, if it's INSERT/UPDATE/DELETE/MERGE with RETURNING, it + * returns that. Otherwise, the function return type must be VOID. * * Note: eventually replace this test with QueryReturnsTuples? We'd need * a more general method of determining the output type, though. Also, it @@ -1681,7 +1681,8 @@ check_sql_fn_retval(List *queryTreeLists, else if (parse && (parse->commandType == CMD_INSERT || parse->commandType == CMD_UPDATE || - parse->commandType == CMD_DELETE) && + parse->commandType == CMD_DELETE || + parse->commandType == CMD_MERGE) && parse->returningList) { tlist = parse->returningList; @@ -1695,7 +1696,7 @@ check_sql_fn_retval(List *queryTreeLists, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), - errdetail("Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING."))); + errdetail("Function's final statement must be SELECT or INSERT/UPDATE/DELETE/MERGE RETURNING."))); return false; /* keep compiler quiet */ } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 9351fbcf494..4abfe82f7fb 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -36,8 +36,7 @@ * RETURNING tuple after completing each row insert, update, or delete. * It must be called again to continue the operation. Without RETURNING, * we just loop within the node until all the work is done, then - * return NULL. This avoids useless call/return overhead. (MERGE does - * not support RETURNING.) + * return NULL. This avoids useless call/return overhead. */ #include "postgres.h" @@ -85,9 +84,6 @@ typedef struct ModifyTableContext */ TupleTableSlot *planSlot; - /* MERGE specific */ - MergeActionState *relaction; /* MERGE action in progress */ - /* * Information about the changes that were made concurrently to a tuple * being updated or deleted @@ -150,14 +146,15 @@ static TupleTableSlot *ExecMerge(ModifyTableContext *context, HeapTuple oldtuple, bool canSetTag); static void ExecInitMerge(ModifyTableState *mtstate, EState *estate); -static bool ExecMergeMatched(ModifyTableContext *context, - ResultRelInfo *resultRelInfo, - ItemPointer tupleid, - HeapTuple oldtuple, - bool canSetTag); -static void ExecMergeNotMatched(ModifyTableContext *context, - ResultRelInfo *resultRelInfo, - bool canSetTag); +static TupleTableSlot *ExecMergeMatched(ModifyTableContext *context, + ResultRelInfo *resultRelInfo, + ItemPointer tupleid, + HeapTuple oldtuple, + bool canSetTag, + bool *matched); +static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context, + ResultRelInfo *resultRelInfo, + bool canSetTag); /* @@ -977,7 +974,7 @@ ExecInsert(ModifyTableContext *context, if (mtstate->operation == CMD_UPDATE) wco_kind = WCO_RLS_UPDATE_CHECK; else if (mtstate->operation == CMD_MERGE) - wco_kind = (context->relaction->mas_action->commandType == CMD_UPDATE) ? + wco_kind = (mtstate->mt_merge_action->mas_action->commandType == CMD_UPDATE) ? WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK; else wco_kind = WCO_RLS_INSERT_CHECK; @@ -1831,7 +1828,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context, * additional rechecking, and might end up executing a different * action entirely). */ - if (context->relaction != NULL) + if (mtstate->operation == CMD_MERGE) return *tmresult == TM_Ok; else if (TupIsNull(epqslot)) return true; @@ -2072,7 +2069,7 @@ lreplace: * No luck, a retry is needed. If running MERGE, we do not do so * here; instead let it handle that on its own rules. */ - if (context->relaction != NULL) + if (context->mtstate->operation == CMD_MERGE) return result; /* @@ -2713,6 +2710,7 @@ static TupleTableSlot * ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag) { + TupleTableSlot *rslot = NULL; bool matched; /*----- @@ -2761,19 +2759,18 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ matched = tupleid != NULL || oldtuple != NULL; if (matched) - matched = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple, - canSetTag); + rslot = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple, + canSetTag, &matched); /* - * Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched() - * returned "false", indicating the previously MATCHED tuple no longer - * matches. + * Deal with the NOT MATCHED case (either a NOT MATCHED tuple from the + * join, or a previously MATCHED tuple for which ExecMergeMatched() set + * "matched" to false, indicating that it no longer matches). */ if (!matched) - ExecMergeNotMatched(context, resultRelInfo, canSetTag); + rslot = ExecMergeNotMatched(context, resultRelInfo, canSetTag); - /* No RETURNING support yet */ - return NULL; + return rslot; } /* @@ -2785,8 +2782,8 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * We start from the first WHEN MATCHED action and check if the WHEN quals * pass, if any. If the WHEN quals for the first action do not pass, we * check the second, then the third and so on. If we reach to the end, no - * action is taken and we return true, indicating that no further action is - * required for this tuple. + * action is taken and "matched" is set to true, indicating that no further + * action is required for this tuple. * * If we do find a qualifying action, then we attempt to execute the action. * @@ -2795,16 +2792,18 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * with individual actions are evaluated by this routine via ExecQual, while * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the * updated tuple still passes the join quals, then we restart from the first - * action to look for a qualifying action. Otherwise, we return false -- - * meaning that a NOT MATCHED action must now be executed for the current - * source tuple. + * action to look for a qualifying action. Otherwise, "matched" is set to + * false -- meaning that a NOT MATCHED action must now be executed for the + * current source tuple. */ -static bool +static TupleTableSlot * ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag) + ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag, + bool *matched) { ModifyTableState *mtstate = context->mtstate; - TupleTableSlot *newslot; + TupleTableSlot *newslot = NULL; + TupleTableSlot *rslot = NULL; EState *estate = context->estate; ExprContext *econtext = mtstate->ps.ps_ExprContext; bool isNull; @@ -2815,7 +2814,10 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * If there are no WHEN MATCHED actions, we are done. */ if (resultRelInfo->ri_matchedMergeAction == NIL) - return true; + { + *matched = true; + return NULL; + } /* * Make tuple and any needed join variables available to ExecQual and @@ -2905,12 +2907,15 @@ lmerge_matched: */ newslot = ExecProject(relaction->mas_proj); - context->relaction = relaction; + mtstate->mt_merge_action = relaction; if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, NULL, newslot, &result)) { if (result == TM_Ok) - return true; /* "do nothing" */ + { + *matched = true; + return NULL; /* "do nothing" */ + } break; /* concurrent update/delete */ } @@ -2920,7 +2925,10 @@ lmerge_matched: { if (!ExecIRUpdateTriggers(estate, resultRelInfo, oldtuple, newslot)) - return true; /* "do nothing" */ + { + *matched = true; + return NULL; /* "do nothing" */ + } } else { @@ -2933,12 +2941,15 @@ lmerge_matched: * cross-partition update was done, then there's nothing * else for us to do --- the UPDATE has been turned into a * DELETE and an INSERT, and we must not perform any of - * the usual post-update tasks. + * the usual post-update tasks. Also, the RETURNING tuple + * (if any) has been projected, so we can just return + * that. */ if (updateCxt.crossPartUpdate) { mtstate->mt_merge_updated += 1; - return true; + *matched = true; + return context->cpUpdateReturningSlot; } } @@ -2951,12 +2962,15 @@ lmerge_matched: break; case CMD_DELETE: - context->relaction = relaction; + mtstate->mt_merge_action = relaction; if (!ExecDeletePrologue(context, resultRelInfo, tupleid, NULL, NULL, &result)) { if (result == TM_Ok) - return true; /* "do nothing" */ + { + *matched = true; + return NULL; /* "do nothing" */ + } break; /* concurrent update/delete */ } @@ -2966,7 +2980,10 @@ lmerge_matched: { if (!ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple)) - return true; /* "do nothing" */ + { + *matched = true; + return NULL; /* "do nothing" */ + } } else result = ExecDeleteAct(context, resultRelInfo, tupleid, @@ -3046,7 +3063,8 @@ lmerge_matched: * If the tuple was already deleted, return to let caller * handle it under NOT MATCHED clauses. */ - return false; + *matched = false; + return NULL; case TM_Updated: { @@ -3092,13 +3110,19 @@ lmerge_matched: * NOT MATCHED actions. */ if (TupIsNull(epqslot)) - return false; + { + *matched = false; + return NULL; + } (void) ExecGetJunkAttribute(epqslot, resultRelInfo->ri_RowIdAttNo, &isNull); if (isNull) - return false; + { + *matched = false; + return NULL; + } /* * When a tuple was updated and migrated to @@ -3133,7 +3157,8 @@ lmerge_matched: * tuple already deleted; tell caller to run NOT * MATCHED actions */ - return false; + *matched = false; + return NULL; case TM_SelfModified: @@ -3161,13 +3186,13 @@ lmerge_matched: /* This shouldn't happen */ elog(ERROR, "attempted to update or delete invisible tuple"); - return false; + return NULL; default: /* see table_tuple_lock call in ExecDelete() */ elog(ERROR, "unexpected table_tuple_lock status: %u", result); - return false; + return NULL; } } @@ -3179,6 +3204,31 @@ lmerge_matched: break; } + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + { + switch (commandType) + { + case CMD_UPDATE: + rslot = ExecProcessReturning(resultRelInfo, newslot, + context->planSlot); + break; + + case CMD_DELETE: + rslot = ExecProcessReturning(resultRelInfo, + resultRelInfo->ri_oldTupleSlot, + context->planSlot); + break; + + case CMD_NOTHING: + break; + + default: + elog(ERROR, "unrecognized commandType: %d", + (int) commandType); + } + } + /* * We've activated one of the WHEN clauses, so we don't search * further. This is required behaviour, not an optimization. @@ -3189,19 +3239,22 @@ lmerge_matched: /* * Successfully executed an action or no qualifying action was found. */ - return true; + *matched = true; + + return rslot; } /* * Execute the first qualifying NOT MATCHED action. */ -static void +static TupleTableSlot * ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, bool canSetTag) { ModifyTableState *mtstate = context->mtstate; ExprContext *econtext = mtstate->ps.ps_ExprContext; List *actionStates = NIL; + TupleTableSlot *rslot = NULL; ListCell *l; /* @@ -3251,10 +3304,10 @@ ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * so we don't need to map the tuple here. */ newslot = ExecProject(action->mas_proj); - context->relaction = action; + mtstate->mt_merge_action = action; - (void) ExecInsert(context, mtstate->rootResultRelInfo, newslot, - canSetTag, NULL, NULL); + rslot = ExecInsert(context, mtstate->rootResultRelInfo, + newslot, canSetTag, NULL, NULL); mtstate->mt_merge_inserted += 1; break; case CMD_NOTHING: @@ -3270,6 +3323,8 @@ ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ break; } + + return rslot; } /* @@ -3732,9 +3787,17 @@ ExecModifyTable(PlanState *pstate) { EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); - ExecMerge(&context, node->resultRelInfo, NULL, NULL, - node->canSetTag); - continue; /* no RETURNING support yet */ + slot = ExecMerge(&context, node->resultRelInfo, + NULL, NULL, node->canSetTag); + + /* + * If we got a RETURNING result, return it to the caller. + * We'll continue the work on next call. + */ + if (slot) + return slot; + + continue; /* continue with the next tuple */ } elog(ERROR, "tableoid is NULL"); @@ -3811,9 +3874,17 @@ ExecModifyTable(PlanState *pstate) { EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); - ExecMerge(&context, node->resultRelInfo, NULL, NULL, - node->canSetTag); - continue; /* no RETURNING support yet */ + slot = ExecMerge(&context, node->resultRelInfo, + NULL, NULL, node->canSetTag); + + /* + * If we got a RETURNING result, return it to the + * caller. We'll continue the work on next call. + */ + if (slot) + return slot; + + continue; /* continue with the next tuple */ } elog(ERROR, "ctid is NULL"); @@ -3860,9 +3931,17 @@ ExecModifyTable(PlanState *pstate) { EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); - ExecMerge(&context, node->resultRelInfo, NULL, NULL, - node->canSetTag); - continue; /* no RETURNING support yet */ + slot = ExecMerge(&context, node->resultRelInfo, + NULL, NULL, node->canSetTag); + + /* + * If we got a RETURNING result, return it to the + * caller. We'll continue the work on next call. + */ + if (slot) + return slot; + + continue; /* continue with the next tuple */ } elog(ERROR, "wholerow is NULL"); @@ -3924,7 +4003,6 @@ ExecModifyTable(PlanState *pstate) } slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot, oldSlot); - context.relaction = NULL; /* Now apply the update. */ slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple, diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 85857715bd8..a97a7e3bd4e 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2032,6 +2032,8 @@ SPI_result_code_string(int code) return "SPI_OK_TD_REGISTER"; case SPI_OK_MERGE: return "SPI_OK_MERGE"; + case SPI_OK_MERGE_RETURNING: + return "SPI_OK_MERGE_RETURNING"; } /* Unrecognized code ... return something useful ... */ sprintf(buf, "Unrecognized SPI code %d", code); @@ -2885,7 +2887,10 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) res = SPI_OK_UPDATE; break; case CMD_MERGE: - res = SPI_OK_MERGE; + if (queryDesc->plannedstmt->hasReturning) + res = SPI_OK_MERGE_RETURNING; + else + res = SPI_OK_MERGE; break; default: return SPI_ERROR_OPUNKNOWN; diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 0c448422e20..2a7d84f046b 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1986,6 +1986,12 @@ llvm_compile_expr(ExprState *state) break; } + case EEOP_MERGE_SUPPORT_FUNC: + build_EvalXFunc(b, mod, "ExecEvalMergeSupportFunc", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_SUBPLAN: build_EvalXFunc(b, mod, "ExecEvalSubPlan", v_state, op, v_econtext); diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 47c9daf4023..7d7aeee1f2b 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -155,6 +155,7 @@ void *referenced_functions[] = ExecEvalFuncExprFusage, ExecEvalFuncExprStrictFusage, ExecEvalGroupingFunc, + ExecEvalMergeSupportFunc, ExecEvalMinMax, ExecEvalNextValueExpr, ExecEvalParamExec, diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 6ba8e732569..5b702809aec 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -66,6 +66,9 @@ exprType(const Node *expr) case T_WindowFunc: type = ((const WindowFunc *) expr)->wintype; break; + case T_MergeSupportFunc: + type = ((const MergeSupportFunc *) expr)->msftype; + break; case T_SubscriptingRef: type = ((const SubscriptingRef *) expr)->refrestype; break; @@ -809,6 +812,9 @@ exprCollation(const Node *expr) case T_WindowFunc: coll = ((const WindowFunc *) expr)->wincollid; break; + case T_MergeSupportFunc: + coll = ((const MergeSupportFunc *) expr)->msfcollid; + break; case T_SubscriptingRef: coll = ((const SubscriptingRef *) expr)->refcollid; break; @@ -1084,6 +1090,9 @@ exprSetCollation(Node *expr, Oid collation) case T_WindowFunc: ((WindowFunc *) expr)->wincollid = collation; break; + case T_MergeSupportFunc: + ((MergeSupportFunc *) expr)->msfcollid = collation; + break; case T_SubscriptingRef: ((SubscriptingRef *) expr)->refcollid = collation; break; @@ -1342,6 +1351,9 @@ exprLocation(const Node *expr) /* function name should always be the first thing */ loc = ((const WindowFunc *) expr)->location; break; + case T_MergeSupportFunc: + loc = ((const MergeSupportFunc *) expr)->location; + break; case T_SubscriptingRef: /* just use container argument's location */ loc = exprLocation((Node *) ((const SubscriptingRef *) expr)->refexpr); @@ -2034,6 +2046,7 @@ expression_tree_walker_impl(Node *node, case T_RangeTblRef: case T_SortGroupClause: case T_CTESearchClause: + case T_MergeSupportFunc: /* primitive node types with no expression subnodes */ break; case T_WithCheckOption: @@ -2868,6 +2881,7 @@ expression_tree_mutator_impl(Node *node, case T_RangeTblRef: case T_SortGroupClause: case T_CTESearchClause: + case T_MergeSupportFunc: return (Node *) copyObject(node); case T_WithCheckOption: { @@ -3832,6 +3846,7 @@ raw_expression_tree_walker_impl(Node *node, case T_ParamRef: case T_A_Const: case T_A_Star: + case T_MergeSupportFunc: /* primitive node types with no subnodes */ break; case T_Alias: @@ -4052,6 +4067,8 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(stmt->mergeWhenClauses)) return true; + if (WALK(stmt->returningList)) + return true; if (WALK(stmt->withClause)) return true; } diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 47e14723d2b..b9e0c960bd3 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1855,7 +1855,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, /* * Replace correlation vars (uplevel vars) with Params. * - * Uplevel PlaceHolderVars and aggregates are replaced, too. + * Uplevel PlaceHolderVars, aggregates, GROUPING() expressions, and + * MergeSupportFuncs are replaced, too. * * Note: it is critical that this runs immediately after SS_process_sublinks. * Since we do not recurse into the arguments of uplevel PHVs and aggregates, @@ -1909,6 +1910,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) if (((GroupingFunc *) node)->agglevelsup > 0) return (Node *) replace_outer_grouping(root, (GroupingFunc *) node); } + if (IsA(node, MergeSupportFunc)) + { + if (root->parse->commandType != CMD_MERGE) + return (Node *) replace_outer_merge_support(root, + (MergeSupportFunc *) node); + } return expression_tree_mutator(node, replace_correlation_vars_mutator, (void *) root); diff --git a/src/backend/optimizer/util/paramassign.c b/src/backend/optimizer/util/paramassign.c index a58da7c57ea..f461fedf194 100644 --- a/src/backend/optimizer/util/paramassign.c +++ b/src/backend/optimizer/util/paramassign.c @@ -308,6 +308,57 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp) } /* + * Generate a Param node to replace the given MergeSupportFunc expression + * which is expected to be in the RETURNING list of an upper-level MERGE + * query. Record the need for the MergeSupportFunc in the proper upper-level + * root->plan_params. + */ +Param * +replace_outer_merge_support(PlannerInfo *root, MergeSupportFunc *msf) +{ + Param *retval; + PlannerParamItem *pitem; + Oid ptype = exprType((Node *) msf); + + Assert(root->parse->commandType != CMD_MERGE); + + /* + * The parser should have ensured that the MergeSupportFunc is in the + * RETURNING list of an upper-level MERGE query, so find that query. + */ + do + { + root = root->parent_root; + if (root == NULL) + elog(ERROR, "MergeSupportFunc found outside MERGE"); + } while (root->parse->commandType != CMD_MERGE); + + /* + * It does not seem worthwhile to try to de-duplicate references to outer + * MergeSupportFunc expressions. Just make a new slot every time. + */ + msf = copyObject(msf); + + pitem = makeNode(PlannerParamItem); + pitem->item = (Node *) msf; + pitem->paramId = list_length(root->glob->paramExecTypes); + root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, + ptype); + + root->plan_params = lappend(root->plan_params, pitem); + + retval = makeNode(Param); + retval->paramkind = PARAM_EXEC; + retval->paramid = pitem->paramId; + retval->paramtype = ptype; + retval->paramtypmod = -1; + retval->paramcollid = InvalidOid; + retval->location = msf->location; + + return retval; +} + +/* * Generate a Param node to replace the given Var, * which is expected to come from some upper NestLoop plan node. * Record the need for the Var in root->curOuterParams. diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 7f23d18b370..40ea19e6f10 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -72,7 +72,6 @@ static void determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist); static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); -static List *transformReturningList(ParseState *pstate, List *returningList); static Query *transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt); static Query *transformDeclareCursorStmt(ParseState *pstate, @@ -551,7 +550,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -978,7 +978,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* Process RETURNING, if any. */ if (stmt->returningList) qry->returningList = transformReturningList(pstate, - stmt->returningList); + stmt->returningList, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -2454,7 +2455,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_RETURNING); /* * Now we are done with SELECT-like processing, and can get on with @@ -2551,10 +2553,11 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) /* * transformReturningList - - * handle a RETURNING clause in INSERT/UPDATE/DELETE + * handle a RETURNING clause in INSERT/UPDATE/DELETE/MERGE */ -static List * -transformReturningList(ParseState *pstate, List *returningList) +List * +transformReturningList(ParseState *pstate, List *returningList, + ParseExprKind exprKind) { List *rlist; int save_next_resno; @@ -2571,7 +2574,7 @@ transformReturningList(ParseState *pstate, List *returningList) pstate->p_next_resno = 1; /* transform RETURNING identically to a SELECT targetlist */ - rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING); + rlist = transformTargetList(pstate, returningList, exprKind); /* * Complain if the nonempty tlist expanded to nothing (which is possible diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3ad99fffe1d..39a801a1c38 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -733,7 +733,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD + MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE @@ -12374,6 +12374,7 @@ MergeStmt: USING table_ref ON a_expr merge_when_list + returning_clause { MergeStmt *m = makeNode(MergeStmt); @@ -12382,6 +12383,7 @@ MergeStmt: m->sourceRelation = $6; m->joinCondition = $8; m->mergeWhenClauses = $9; + m->returningList = $10; $$ = (Node *) m; } @@ -15795,6 +15797,14 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *) n; } + | MERGE_ACTION '(' ')' + { + MergeSupportFunc *m = makeNode(MergeSupportFunc); + + m->msftype = TEXTOID; + m->location = @1; + $$ = (Node *) m; + } ; @@ -17492,6 +17502,7 @@ col_name_keyword: | JSON_SCALAR | JSON_SERIALIZE | LEAST + | MERGE_ACTION | NATIONAL | NCHAR | NONE @@ -17881,6 +17892,7 @@ bare_label_keyword: | MATERIALIZED | MAXVALUE | MERGE + | MERGE_ACTION | METHOD | MINVALUE | MODE diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 9d151a880b8..bee7d8346a3 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -468,6 +468,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: @@ -915,6 +916,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 3c88c9abbae..6826d4f36a1 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -126,13 +126,6 @@ transformWithClause(ParseState *pstate, WithClause *withClause) CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); ListCell *rest; - /* MERGE is allowed by parser, but unimplemented. Reject for now */ - if (IsA(cte->ctequery, MergeStmt)) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("MERGE not supported in WITH query"), - parser_errposition(pstate, cte->location)); - for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc)) { CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest); @@ -153,7 +146,8 @@ transformWithClause(ParseState *pstate, WithClause *withClause) /* must be a data-modifying statement */ Assert(IsA(cte->ctequery, InsertStmt) || IsA(cte->ctequery, UpdateStmt) || - IsA(cte->ctequery, DeleteStmt)); + IsA(cte->ctequery, DeleteStmt) || + IsA(cte->ctequery, MergeStmt)); pstate->p_hasModifyingCTE = true; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 9300c7b9abc..d44b1f2ab2f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -54,6 +54,7 @@ static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a); static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a); static Node *transformAExprIn(ParseState *pstate, A_Expr *a); static Node *transformAExprBetween(ParseState *pstate, A_Expr *a); +static Node *transformMergeSupportFunc(ParseState *pstate, MergeSupportFunc *f); static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a); static Node *transformFuncCall(ParseState *pstate, FuncCall *fn); static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); @@ -227,6 +228,11 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformGroupingFunc(pstate, (GroupingFunc *) expr); break; + case T_MergeSupportFunc: + result = transformMergeSupportFunc(pstate, + (MergeSupportFunc *) expr); + break; + case T_NamedArgExpr: { NamedArgExpr *na = (NamedArgExpr *) expr; @@ -541,6 +547,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_LIMIT: case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CHECK_CONSTRAINT: @@ -1354,6 +1361,31 @@ transformAExprBetween(ParseState *pstate, A_Expr *a) } static Node * +transformMergeSupportFunc(ParseState *pstate, MergeSupportFunc *f) +{ + /* + * All we need to do is check that we're in the RETURNING list of a MERGE + * command. If so, we just return the node as-is. + */ + if (pstate->p_expr_kind != EXPR_KIND_MERGE_RETURNING) + { + ParseState *parent_pstate = pstate->parentParseState; + + while (parent_pstate && + parent_pstate->p_expr_kind != EXPR_KIND_MERGE_RETURNING) + parent_pstate = parent_pstate->parentParseState; + + if (!parent_pstate) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("MERGE_ACTION() can only be used in the RETURNING list of a MERGE command"), + parser_errposition(pstate, f->location)); + } + + return (Node *) f; +} + +static Node * transformBoolExpr(ParseState *pstate, BoolExpr *a) { List *args = NIL; @@ -1767,6 +1799,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_LIMIT: case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CYCLE_MARK: @@ -3115,6 +3148,7 @@ ParseExprKindName(ParseExprKind exprKind) case EXPR_KIND_OFFSET: return "OFFSET"; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: return "RETURNING"; case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index fdb3e6df338..0cbc950c95c 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2599,6 +2599,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) errkind = true; break; case EXPR_KIND_RETURNING: + case EXPR_KIND_MERGE_RETURNING: errkind = true; break; case EXPR_KIND_VALUES: diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index a7d8ba7e98c..04ed5e66dda 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -234,6 +234,10 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) */ qry->jointree = makeFromExpr(pstate->p_joinlist, joinExpr); + /* Transform the RETURNING list, if any */ + qry->returningList = transformReturningList(pstate, stmt->returningList, + EXPR_KIND_MERGE_RETURNING); + /* * We now have a good query shape, so now look at the WHEN conditions and * action targetlists. @@ -391,9 +395,6 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) qry->mergeActionList = mergeActionList; - /* RETURNING could potentially be added in the future, but not in SQL std */ - qry->returningList = NULL; - qry->hasTargetSRFs = false; qry->hasSubLinks = pstate->p_hasSubLinks; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 6f5d9e26925..427b7325db8 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2341,9 +2341,10 @@ addRangeTableEntryForCTE(ParseState *pstate, cte->cterefcount++; /* - * We throw error if the CTE is INSERT/UPDATE/DELETE without RETURNING. - * This won't get checked in case of a self-reference, but that's OK - * because data-modifying CTEs aren't allowed to be recursive anyhow. + * We throw error if the CTE is INSERT/UPDATE/DELETE/MERGE without + * RETURNING. This won't get checked in case of a self-reference, but + * that's OK because data-modifying CTEs aren't allowed to be recursive + * anyhow. */ if (IsA(cte->ctequery, Query)) { diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 5b92502b217..ea522b932b2 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1820,6 +1820,10 @@ FigureColnameInternal(Node *node, char **name) /* make GROUPING() act like a regular function */ *name = "grouping"; return 2; + case T_MergeSupportFunc: + /* make MERGE_ACTION() act like a regular function */ + *name = "merge_action"; + return 2; case T_SubLink: switch (((SubLink *) node)->subLinkType) { diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 7a46e8b3541..9fd05b15e73 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -3833,9 +3833,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) ListCell *lc1; /* - * First, recursively process any insert/update/delete statements in WITH - * clauses. (We have to do this first because the WITH clauses may get - * copied into rule actions below.) + * First, recursively process any insert/update/delete/merge statements in + * WITH clauses. (We have to do this first because the WITH clauses may + * get copied into rule actions below.) */ foreach(lc1, parsetree->cteList) { @@ -3860,7 +3860,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) if (!(ctequery->commandType == CMD_SELECT || ctequery->commandType == CMD_UPDATE || ctequery->commandType == CMD_INSERT || - ctequery->commandType == CMD_DELETE)) + ctequery->commandType == CMD_DELETE || + ctequery->commandType == CMD_MERGE)) { /* * Currently it could only be NOTIFY; this error message will diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index 450af27c7f5..59fd305dd7b 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -384,10 +384,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, * on the final action we take. * * We already fetched the SELECT policies above, to check existing rows, - * but we must also check that new rows created by UPDATE actions are - * visible, if SELECT rights are required for this relation. We don't do - * this for INSERT actions, since an INSERT command would only do this - * check if it had a RETURNING list, and MERGE does not support RETURNING. + * but we must also check that new rows created by INSERT/UPDATE actions + * are visible, if SELECT rights are required. For INSERT actions, we only + * do this if RETURNING is specified, to be consistent with a plain INSERT + * command, which can only require SELECT rights when RETURNING is used. * * We don't push the UPDATE/DELETE USING quals to the RTE because we don't * really want to apply them while scanning the relation since we don't @@ -409,6 +409,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, List *merge_delete_restrictive_policies; List *merge_insert_permissive_policies; List *merge_insert_restrictive_policies; + List *merge_select_permissive_policies = NIL; + List *merge_select_restrictive_policies = NIL; /* * Fetch the UPDATE policies and set them up to execute on the @@ -446,9 +448,6 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, */ if (perminfo->requiredPerms & ACL_SELECT) { - List *merge_select_permissive_policies; - List *merge_select_restrictive_policies; - get_policies_for_relation(rel, CMD_SELECT, user_id, &merge_select_permissive_policies, &merge_select_restrictive_policies); @@ -497,6 +496,21 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, withCheckOptions, hasSubLinks, false); + + /* + * Add ALL/SELECT policies as WCO_RLS_INSERT_CHECK WCOs, to ensure + * that the inserted row is visible when executing an INSERT action, + * if RETURNING is specified and SELECT rights are required for this + * relation. + */ + if (perminfo->requiredPerms & ACL_SELECT && root->returningList) + add_with_check_options(rel, rt_index, + WCO_RLS_INSERT_CHECK, + merge_select_permissive_policies, + merge_select_restrictive_policies, + withCheckOptions, + hasSubLinks, + true); } table_close(rel, NoLock); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 83f86a42f79..fa66b8017ed 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -2138,11 +2138,10 @@ QueryReturnsTuples(Query *parsetree) case CMD_SELECT: /* returns tuples */ return true; - case CMD_MERGE: - return false; case CMD_INSERT: case CMD_UPDATE: case CMD_DELETE: + case CMD_MERGE: /* the forms with RETURNING return tuples */ if (parsetree->returningList) return true; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2231752613e..f2893d40861 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7199,8 +7199,13 @@ get_merge_query_def(Query *query, deparse_context *context, appendStringInfoString(buf, "DO NOTHING"); } - /* No RETURNING support in MERGE yet */ - Assert(query->returningList == NIL); + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL, colNamesVisible); + } } @@ -8300,6 +8305,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: case T_GroupingFunc: case T_WindowFunc: + case T_MergeSupportFunc: case T_FuncExpr: case T_JsonConstructorExpr: /* function-like: name(..) or name[..] */ @@ -8654,6 +8660,10 @@ get_rule_expr(Node *node, deparse_context *context, get_windowfunc_expr((WindowFunc *) node, context); break; + case T_MergeSupportFunc: + appendStringInfoString(buf, "MERGE_ACTION()"); + break; + case T_SubscriptingRef: { SubscriptingRef *sbsref = (SubscriptingRef *) node; |