diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 40a54ad0bd..50eb019da5 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1042,7 +1042,8 @@ CopyFrom(CopyFromState cstate) */ if (resultRelInfo->ri_RelationDesc->rd_rel->relispartition && (proute == NULL || has_before_insert_row_trig)) - ExecPartitionCheck(resultRelInfo, myslot, estate, true); + ExecPartitionCheck(resultRelInfo, myslot, estate, NULL, + true); /* Store the slot in the multi-insert buffer, when enabled. */ if (insertMethod == CIM_MULTI || leafpart_use_multi_insert) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 07c73f39de..8fc647cd0e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2288,7 +2288,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, * longer fits the partition. Verify that. */ if (trigger->tgisclone && - !ExecPartitionCheck(relinfo, slot, estate, false)) + !ExecPartitionCheck(relinfo, slot, estate, NULL, false)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported"), diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index b3ce4bae53..8ddbc6d0c2 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -44,6 +44,7 @@ #include "access/transam.h" #include "access/xact.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_publication.h" #include "commands/matview.h" #include "commands/trigger.h" @@ -52,6 +53,8 @@ #include "foreign/fdwapi.h" #include "jit/jit.h" #include "mb/pg_wchar.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "miscadmin.h" #include "parser/parsetree.h" #include "storage/bufmgr.h" @@ -1686,6 +1689,32 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, return NULL; } +/* + * Replaces the occurrence of cxt->matchexpr in the expression tree given by + * 'node' by an OUTER var with provided attribute number. + */ +typedef struct +{ + Expr *matchexpr; + AttrNumber varattno; +} replace_partexpr_with_dummy_var_context; + +static Node * +replace_partexpr_with_dummy_var(Node *node, + replace_partexpr_with_dummy_var_context *cxt) +{ + if (node == NULL) + return NULL; + + if (equal(node, cxt->matchexpr)) + return (Node *) makeVar(OUTER_VAR, cxt->varattno, + exprType(node), exprTypmod(node), + exprCollation(node), 0); + + return expression_tree_mutator(node, replace_partexpr_with_dummy_var, + (void *) cxt); +} + /* * ExecPartitionCheck --- check that tuple meets the partition constraint. * @@ -1695,7 +1724,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, */ bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, - EState *estate, bool emitError) + EState *estate, Relation parentrel, bool emitError) { ExprContext *econtext; bool success; @@ -1716,6 +1745,50 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); List *qual = RelationGetPartitionQual(resultRelInfo->ri_RelationDesc); + /* + * If we have been passed the parent relation, optimize the evaluation + * of partition key expressions. The way we do that is by replacing + * any occurrences of the individual expressions in this relation's + * partition constraint by dummy Vars marked as coming from the + * "OUTER" relation. Then when actually executing such modified + * partition constraint tree, we feed the actual partition expression + * values via econtext->ecxt_outertuple; see below. + */ + if (parentrel) + { + replace_partexpr_with_dummy_var_context cxt; + List *partexprs = RelationGetPartitionKey(parentrel)->partexprs; + ListCell *lc; + AttrNumber attrno = 1; + TupleDesc partexprs_tupdesc; + + partexprs_tupdesc = CreateTemplateTupleDesc(list_length(partexprs)); + + partexprs = map_partition_varattnos(partexprs, 1, + resultRelInfo->ri_RelationDesc, + parentrel); + foreach(lc, partexprs) + { + Expr *expr = lfirst(lc); + + cxt.matchexpr = expr; + cxt.varattno = attrno; + qual = (List *) replace_partexpr_with_dummy_var((Node *) qual, + &cxt); + + resultRelInfo->ri_partitionKeyExprs = + lappend(resultRelInfo->ri_partitionKeyExprs, + ExecPrepareExpr(expr, estate)); + TupleDescInitEntry(partexprs_tupdesc, attrno, NULL, + exprType((Node *) expr), + exprTypmod((Node *) expr), 0); + attrno++; + } + + resultRelInfo->ri_partitionKeyExprsSlot = + ExecInitExtraTupleSlot(estate, partexprs_tupdesc, &TTSOpsVirtual); + } + resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate); MemoryContextSwitchTo(oldcxt); } @@ -1729,6 +1802,33 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, /* Arrange for econtext's scan tuple to be the tuple under test */ econtext->ecxt_scantuple = slot; + if (resultRelInfo->ri_partitionKeyExprs) + { + TupleTableSlot *partexprs_slot = resultRelInfo->ri_partitionKeyExprsSlot; + Datum *values; + bool *isnull; + ListCell *lc; + AttrNumber attrno = 1; + + Assert(partexprs_slot != NULL); + ExecClearTuple(partexprs_slot); + + values = partexprs_slot->tts_values; + isnull = partexprs_slot->tts_isnull; + + foreach(lc, resultRelInfo->ri_partitionKeyExprs) + { + ExprState *partexpr = lfirst(lc); + + values[attrno-1] = ExecEvalExprSwitchContext(partexpr, econtext, + &isnull[attrno-1]); + attrno++; + } + ExecStoreVirtualTuple(partexprs_slot); + + econtext->ecxt_outertuple = partexprs_slot; + } + /* * As in case of the catalogued constraints, we treat a NULL result as * success here, not a failure. diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 2348eb3154..eaabdc6ee1 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -287,7 +287,7 @@ ExecFindPartition(ModifyTableState *mtstate, * routing the tuple if it doesn't belong in the root table itself. */ if (rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition) - ExecPartitionCheck(rootResultRelInfo, slot, estate, true); + ExecPartitionCheck(rootResultRelInfo, slot, estate, NULL, true); /* start with the root partitioned table */ dispatch = pd[0]; @@ -298,6 +298,8 @@ ExecFindPartition(ModifyTableState *mtstate, CHECK_FOR_INTERRUPTS(); + rel = dispatch->reldesc; + /* * Check if the saved partition accepts this tuple by evaluating its * partition constraint against the tuple. If it does, we save a trip @@ -317,7 +319,7 @@ ExecFindPartition(ModifyTableState *mtstate, rri->ri_PartitionTupleSlot); else tmpslot = rootslot; - if (ExecPartitionCheck(rri, tmpslot, estate, false)) + if (ExecPartitionCheck(rri, tmpslot, estate, rel, false)) { /* and restore ecxt's scantuple */ ecxt->ecxt_scantuple = ecxt_scantuple_saved; @@ -327,7 +329,6 @@ ExecFindPartition(ModifyTableState *mtstate, dispatch->lastPartInfo = rri = NULL; } - rel = dispatch->reldesc; partdesc = dispatch->partdesc; /* @@ -513,7 +514,7 @@ ExecFindPartition(ModifyTableState *mtstate, slot = rootslot; } - ExecPartitionCheck(rri, slot, estate, true); + ExecPartitionCheck(rri, slot, estate, NULL, true); } } diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 1e285e0349..9f37f55090 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -437,7 +437,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo, if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); if (rel->rd_rel->relispartition) - ExecPartitionCheck(resultRelInfo, slot, estate, true); + ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true); /* OK, store the tuple and create index entries for it */ simple_table_tuple_insert(resultRelInfo->ri_RelationDesc, slot); @@ -505,7 +505,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); if (rel->rd_rel->relispartition) - ExecPartitionCheck(resultRelInfo, slot, estate, true); + ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true); simple_table_tuple_update(rel, tid, slot, estate->es_snapshot, &update_indexes); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 379b056310..c46fff0f7e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -791,7 +791,7 @@ ExecInsert(ModifyTableState *mtstate, (resultRelInfo->ri_RootResultRelInfo == NULL || (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row))) - ExecPartitionCheck(resultRelInfo, slot, estate, true); + ExecPartitionCheck(resultRelInfo, slot, estate, NULL, true); if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0) { @@ -1710,7 +1710,7 @@ lreplace:; */ partition_constraint_failed = resultRelationDesc->rd_rel->relispartition && - !ExecPartitionCheck(resultRelInfo, slot, estate, false); + !ExecPartitionCheck(resultRelInfo, slot, estate, NULL, false); if (!partition_constraint_failed && resultRelInfo->ri_WithCheckOptions != NIL) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 6ba447ea97..ee4f7bf06f 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -1747,7 +1747,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata, */ if (!partrel->rd_rel->relispartition || ExecPartitionCheck(partrelinfo, remoteslot_part, estate, - false)) + NULL, false)) { /* * Yes, so simply UPDATE the partition. We don't call diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 3dc03c913e..c44ac1d7f3 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -207,7 +207,8 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, - TupleTableSlot *slot, EState *estate, bool emitError); + TupleTableSlot *slot, EState *estate, + Relation parentrel, bool emitError); extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 7795a69490..9984171576 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -496,6 +496,13 @@ typedef struct ResultRelInfo /* partition check expression state (NULL if not set up yet) */ ExprState *ri_PartitionCheckExpr; + /* + * Information used by ExecPartitionCheck() to optimize some cases where + * the parent's partition key contains arbitrary expressions. + */ + List *ri_partitionKeyExprs; + TupleTableSlot *ri_partitionKeyExprsSlot; + /* * Information needed by tuple routing target relations *