diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6af224fa217..3ddd604888a 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1922,7 +1922,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, PgFdwModifyState *fmstate; ModifyTable *plan = castNode(ModifyTable, mtstate->ps.plan); EState *estate = mtstate->ps.state; - Index resultRelation = resultRelInfo->ri_RangeTableIndex; + Index resultRelation; Relation rel = resultRelInfo->ri_RelationDesc; RangeTblEntry *rte; TupleDesc tupdesc = RelationGetDescr(rel); @@ -1973,7 +1973,8 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, } /* - * If the foreign table is a partition, we need to create a new RTE + * If the foreign table is a partition that doesn't have a corresponding + * RTE entry, we need to create a new RTE * describing the foreign table for use by deparseInsertSql and * create_foreign_modify() below, after first copying the parent's RTE and * modifying some fields to describe the foreign partition to work on. @@ -1981,9 +1982,11 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, * correspond to this partition if it is one of the UPDATE subplan target * rels; in that case, we can just use the existing RTE as-is. */ - rte = exec_rt_fetch(resultRelation, estate); - if (rte->relid != RelationGetRelid(rel)) + if (resultRelInfo->ri_RangeTableIndex == 0) { + ResultRelInfo *rootResultRelInfo = resultRelInfo->ri_RootResultRelInfo; + + rte = exec_rt_fetch(rootResultRelInfo->ri_RangeTableIndex, estate); rte = copyObject(rte); rte->relid = RelationGetRelid(rel); rte->relkind = RELKIND_FOREIGN_TABLE; @@ -1995,8 +1998,15 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, * Vars contained in those expressions. */ if (plan && plan->operation == CMD_UPDATE && - resultRelation == plan->rootRelation) + rootResultRelInfo->ri_RangeTableIndex == plan->rootRelation) resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex; + else + resultRelation = rootResultRelInfo->ri_RangeTableIndex; + } + else + { + resultRelation = resultRelInfo->ri_RangeTableIndex; + rte = exec_rt_fetch(resultRelation, estate); } /* Construct the SQL command string. */ diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c index 3cb0cbefaa3..7cd8cde1277 100644 --- a/src/backend/access/common/tupconvert.c +++ b/src/backend/access/common/tupconvert.c @@ -226,6 +226,57 @@ execute_attr_map_slot(AttrMap *attrMap, return out_slot; } +/* + * Perform conversion of bitmap of columns according to the map. + * + * The input and output bitmaps are offset by + * FirstLowInvalidHeapAttributeNumber to accommodate system cols, like the + * column-bitmaps in RangeTblEntry. + */ +Bitmapset * +execute_attr_map_cols(AttrMap *attrMap, Bitmapset *in_cols) +{ + Bitmapset *out_cols; + int out_attnum; + + /* fast path for the common trivial case */ + if (in_cols == NULL) + return NULL; + + /* + * For each output column, check which input column it corresponds to. + */ + out_cols = NULL; + + for (out_attnum = FirstLowInvalidHeapAttributeNumber + 1; + out_attnum <= attrMap->maplen; + out_attnum++) + { + int in_attnum; + + if (out_attnum < 0) + { + /* System column. No mapping. */ + in_attnum = out_attnum; + } + else if (out_attnum == 0) + continue; + else + { + /* normal user column */ + in_attnum = attrMap->attnums[out_attnum - 1]; + + if (in_attnum == 0) + continue; + } + + if (bms_is_member(in_attnum - FirstLowInvalidHeapAttributeNumber, in_cols)) + out_cols = bms_add_member(out_cols, out_attnum - FirstLowInvalidHeapAttributeNumber); + } + + return out_cols; +} + /* * Free a TupleConversionMap structure. */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index bb2a9517d74..29a1a982e5c 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2804,6 +2804,7 @@ CopyFrom(CopyState cstate) mtstate->ps.state = estate; mtstate->operation = CMD_INSERT; mtstate->resultRelInfo = estate->es_result_relations; + mtstate->rootResultRelInfo = estate->es_result_relations; if (resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 0ad49612d28..20708db9f12 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3681,7 +3681,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors, /* Should we explicitly label target relations? */ labeltargets = (mtstate->mt_nplans > 1 || (mtstate->mt_nplans == 1 && - mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation)); + mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation)); if (labeltargets) ExplainOpenGroup("Target Tables", "Target Tables", false, es); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 29d58c6be51..af0621fbd53 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -69,16 +69,6 @@ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN; /* How many levels deep into trigger execution are we? */ static int MyTriggerDepth = 0; -/* - * Note that similar macros also exist in executor/execMain.c. There does not - * appear to be any good header to put them into, given the structures that - * they use, so we let them be duplicated. Be sure to update all if one needs - * to be changed, however. - */ -#define GetAllUpdatedColumns(relinfo, estate) \ - (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ - exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) - /* Local function prototypes */ static void SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger); static bool GetTupleForTrigger(EState *estate, @@ -2570,7 +2560,10 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) CMD_UPDATE)) return; - updatedCols = GetAllUpdatedColumns(relinfo, estate); + /* statement-level triggers operate on the parent table */ + Assert(relinfo->ri_RootResultRelInfo == NULL); + + updatedCols = ExecGetAllUpdatedCols(relinfo, estate); LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | @@ -2611,10 +2604,13 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + /* statement-level triggers operate on the parent table */ + Assert(relinfo->ri_RootResultRelInfo == NULL); + if (trigdesc && trigdesc->trig_update_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, - GetAllUpdatedColumns(relinfo, estate), + ExecGetAllUpdatedCols(relinfo, estate), transition_capture); } @@ -2684,7 +2680,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; - updatedCols = GetAllUpdatedColumns(relinfo, estate); + updatedCols = ExecGetAllUpdatedCols(relinfo, estate); LocTriggerData.tg_updatedcols = updatedCols; for (i = 0; i < trigdesc->numtriggers; i++) { @@ -2785,7 +2781,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, oldslot, newslot, recheckIndexes, - GetAllUpdatedColumns(relinfo, estate), + ExecGetAllUpdatedCols(relinfo, estate), transition_capture); } } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4fdffad6f35..565262dd27d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -100,20 +100,6 @@ static char *ExecBuildSlotValueDescription(Oid reloid, int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); -/* - * Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does - * not appear to be any good header to put it into, given the structures that - * it uses, so we let them be duplicated. Be sure to update both if one needs - * to be changed, however. - */ -#define GetInsertedColumns(relinfo, estate) \ - (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols) -#define GetUpdatedColumns(relinfo, estate) \ - (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols) -#define GetAllUpdatedColumns(relinfo, estate) \ - (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ - exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) - /* end of local decls */ @@ -1277,7 +1263,7 @@ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, - Relation partition_root, + ResultRelInfo *partition_root_rri, int instrument_options) { List *partition_check = NIL; @@ -1342,7 +1328,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, partition_check = RelationGetPartitionQual(resultRelationDesc); resultRelInfo->ri_PartitionCheck = partition_check; - resultRelInfo->ri_PartitionRoot = partition_root; + resultRelInfo->ri_RootResultRelInfo = partition_root_rri; resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */ resultRelInfo->ri_CopyMultiInsertBuffer = NULL; } @@ -1840,13 +1826,14 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo, * back to the root table's rowtype so that val_desc in the error message * matches the input tuple. */ - if (resultRelInfo->ri_PartitionRoot) + if (resultRelInfo->ri_RootResultRelInfo) { + ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo; TupleDesc old_tupdesc; AttrMap *map; - root_relid = RelationGetRelid(resultRelInfo->ri_PartitionRoot); - tupdesc = RelationGetDescr(resultRelInfo->ri_PartitionRoot); + root_relid = RelationGetRelid(rootrel->ri_RelationDesc); + tupdesc = RelationGetDescr(rootrel->ri_RelationDesc); old_tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); /* a reverse map */ @@ -1859,16 +1846,17 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo, if (map != NULL) slot = execute_attr_map_slot(map, slot, MakeTupleTableSlot(tupdesc, &TTSOpsVirtual)); + modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate), + ExecGetUpdatedCols(rootrel, estate)); } else { root_relid = RelationGetRelid(resultRelInfo->ri_RelationDesc); tupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate), + ExecGetUpdatedCols(resultRelInfo, estate)); } - modifiedCols = bms_union(GetInsertedColumns(resultRelInfo, estate), - GetUpdatedColumns(resultRelInfo, estate)); - val_desc = ExecBuildSlotValueDescription(root_relid, slot, tupdesc, @@ -1901,8 +1889,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo, TupleDesc tupdesc = RelationGetDescr(rel); TupleConstr *constr = tupdesc->constr; Bitmapset *modifiedCols; - Bitmapset *insertedCols; - Bitmapset *updatedCols; Assert(constr || resultRelInfo->ri_PartitionCheck); @@ -1928,12 +1914,12 @@ ExecConstraints(ResultRelInfo *resultRelInfo, * rowtype so that val_desc shown error message matches the * input tuple. */ - if (resultRelInfo->ri_PartitionRoot) + if (resultRelInfo->ri_RootResultRelInfo) { + ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo; AttrMap *map; - rel = resultRelInfo->ri_PartitionRoot; - tupdesc = RelationGetDescr(rel); + tupdesc = RelationGetDescr(rootrel->ri_RelationDesc); /* a reverse map */ map = build_attrmap_by_name_if_req(orig_tupdesc, tupdesc); @@ -1945,11 +1931,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo, if (map != NULL) slot = execute_attr_map_slot(map, slot, MakeTupleTableSlot(tupdesc, &TTSOpsVirtual)); + modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate), + ExecGetUpdatedCols(rootrel, estate)); + rel = rootrel->ri_RelationDesc; } - - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); - modifiedCols = bms_union(insertedCols, updatedCols); + else + modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate), + ExecGetUpdatedCols(resultRelInfo, estate)); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, @@ -1977,13 +1965,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo, Relation orig_rel = rel; /* See the comment above. */ - if (resultRelInfo->ri_PartitionRoot) + if (resultRelInfo->ri_RootResultRelInfo) { + ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo; TupleDesc old_tupdesc = RelationGetDescr(rel); AttrMap *map; - rel = resultRelInfo->ri_PartitionRoot; - tupdesc = RelationGetDescr(rel); + tupdesc = RelationGetDescr(rootrel->ri_RelationDesc); /* a reverse map */ map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc); @@ -1995,11 +1983,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo, if (map != NULL) slot = execute_attr_map_slot(map, slot, MakeTupleTableSlot(tupdesc, &TTSOpsVirtual)); + modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate), + ExecGetUpdatedCols(rootrel, estate)); + rel = rootrel->ri_RelationDesc; } - - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); - modifiedCols = bms_union(insertedCols, updatedCols); + else + modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate), + ExecGetUpdatedCols(resultRelInfo, estate)); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, @@ -2068,8 +2058,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, { char *val_desc; Bitmapset *modifiedCols; - Bitmapset *insertedCols; - Bitmapset *updatedCols; switch (wco->kind) { @@ -2084,13 +2072,13 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, */ case WCO_VIEW_CHECK: /* See the comment in ExecConstraints(). */ - if (resultRelInfo->ri_PartitionRoot) + if (resultRelInfo->ri_RootResultRelInfo) { + ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo; TupleDesc old_tupdesc = RelationGetDescr(rel); AttrMap *map; - rel = resultRelInfo->ri_PartitionRoot; - tupdesc = RelationGetDescr(rel); + tupdesc = RelationGetDescr(rootrel->ri_RelationDesc); /* a reverse map */ map = build_attrmap_by_name_if_req(old_tupdesc, tupdesc); @@ -2102,11 +2090,14 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, if (map != NULL) slot = execute_attr_map_slot(map, slot, MakeTupleTableSlot(tupdesc, &TTSOpsVirtual)); - } - insertedCols = GetInsertedColumns(resultRelInfo, estate); - updatedCols = GetUpdatedColumns(resultRelInfo, estate); - modifiedCols = bms_union(insertedCols, updatedCols); + modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate), + ExecGetUpdatedCols(rootrel, estate)); + rel = rootrel->ri_RelationDesc; + } + else + modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate), + ExecGetUpdatedCols(resultRelInfo, estate)); val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, @@ -2320,7 +2311,7 @@ ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo) * been modified, then we can use a weaker lock, allowing for better * concurrency. */ - updatedCols = GetAllUpdatedColumns(relinfo, estate); + updatedCols = ExecGetAllUpdatedCols(relinfo, estate); keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, INDEX_ATTR_BITMAP_KEY); diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 2e9bb13c536..e64bb2b6052 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -176,7 +176,8 @@ static void ExecInitRoutingInfo(ModifyTableState *mtstate, int partidx); static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate, PartitionTupleRouting *proute, - Oid partoid, PartitionDispatch parent_pd, int partidx); + Oid partoid, PartitionDispatch parent_pd, + int partidx, ResultRelInfo *rootResultRelInfo); static void FormPartitionKeyDatum(PartitionDispatch pd, TupleTableSlot *slot, EState *estate, @@ -238,7 +239,7 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, * partitioned table. */ ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel), - NULL, 0); + NULL, 0, NULL); /* * If performing an UPDATE with tuple routing, we can reuse partition @@ -426,10 +427,11 @@ ExecFindPartition(ModifyTableState *mtstate, * Create the new PartitionDispatch. We pass the current one * in as the parent PartitionDispatch */ - subdispatch = ExecInitPartitionDispatchInfo(mtstate->ps.state, + subdispatch = ExecInitPartitionDispatchInfo(estate, proute, partdesc->oids[partidx], - dispatch, partidx); + dispatch, partidx, + mtstate->rootResultRelInfo); Assert(dispatch->indexes[partidx] >= 0 && dispatch->indexes[partidx] < proute->num_dispatch); @@ -544,7 +546,7 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, * compatible with the root partitioned table's tuple descriptor. When * generating the per-subplan result rels, this was not set. */ - rri->ri_PartitionRoot = proute->partition_root; + rri->ri_RootResultRelInfo = mtstate->rootResultRelInfo; } } @@ -564,8 +566,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, int partidx) { ModifyTable *node = (ModifyTable *) mtstate->ps.plan; - Relation rootrel = rootResultRelInfo->ri_RelationDesc, - partrel; + Relation partrel; + int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc; ResultRelInfo *leaf_part_rri; MemoryContext oldcxt; @@ -579,8 +581,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, leaf_part_rri = makeNode(ResultRelInfo); InitResultRelInfo(leaf_part_rri, partrel, - node ? node->rootRelation : 1, - rootrel, + 0, + rootResultRelInfo, estate->es_instrument); /* @@ -614,7 +616,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, List *wcoList; List *wcoExprs = NIL; ListCell *ll; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; /* * In the case of INSERT on a partitioned table, there is only one @@ -678,7 +679,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot; ExprContext *econtext; List *returningList; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; /* See the comment above for WCO lists. */ Assert((node->operation == CMD_INSERT && @@ -737,7 +737,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, */ if (node && node->onConflictAction != ONCONFLICT_NONE) { - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; TupleDesc partrelDesc = RelationGetDescr(partrel); ExprContext *econtext = mtstate->ps.ps_ExprContext; ListCell *lc; @@ -939,6 +938,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, ResultRelInfo *partRelInfo, int partidx) { + ResultRelInfo *rootRelInfo = partRelInfo->ri_RootResultRelInfo; MemoryContext oldcxt; PartitionRoutingInfo *partrouteinfo; int rri_index; @@ -952,7 +952,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, * partition from the parent's type to the partition's. */ partrouteinfo->pi_RootToPartitionMap = - convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot), + convert_tuples_by_name(RelationGetDescr(rootRelInfo->ri_RelationDesc), RelationGetDescr(partRelInfo->ri_RelationDesc)); /* @@ -985,7 +985,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, { partrouteinfo->pi_PartitionToRootMap = convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc), - RelationGetDescr(partRelInfo->ri_PartitionRoot)); + RelationGetDescr(rootRelInfo->ri_RelationDesc)); } else partrouteinfo->pi_PartitionToRootMap = NULL; @@ -1044,7 +1044,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate, PartitionTupleRouting *proute, Oid partoid, - PartitionDispatch parent_pd, int partidx) + PartitionDispatch parent_pd, int partidx, + ResultRelInfo *rootResultRelInfo) { Relation rel; PartitionDesc partdesc; @@ -1142,7 +1143,7 @@ ExecInitPartitionDispatchInfo(EState *estate, { ResultRelInfo *rri = makeNode(ResultRelInfo); - InitResultRelInfo(rri, rel, 1, proute->partition_root, 0); + InitResultRelInfo(rri, rel, 0, rootResultRelInfo, 0); proute->nonleaf_partitions[dispatchidx] = rri; } else diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d0e65b86473..57771ba78f7 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -51,6 +51,7 @@ #include "access/tableam.h" #include "access/transam.h" #include "executor/executor.h" +#include "executor/execPartition.h" #include "jit/jit.h" #include "mb/pg_wchar.h" #include "miscadmin.h" @@ -1203,3 +1204,91 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo) return relInfo->ri_ReturningSlot; } + +/* Return a bitmap representing columns being inserted */ +Bitmapset * +ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate) +{ + /* + * The columns are stored in the range table entry. If this ResultRelInfo + * doesn't have an entry in the range table (i.e. if it represents a + * partition routing target), fetch the parent's RTE and map the columns + * to the order they are in the partition. + */ + if (relinfo->ri_RangeTableIndex != 0) + { + RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate); + + return rte->insertedCols; + } + else + { + ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo; + RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate); + PartitionRoutingInfo *partrouteinfo = relinfo->ri_PartitionInfo; + + if (partrouteinfo->pi_RootToPartitionMap != NULL) + return execute_attr_map_cols(partrouteinfo->pi_RootToPartitionMap->attrMap, + rte->insertedCols); + else + return rte->insertedCols; + } +} + +/* Return a bitmap representing columns being updated */ +Bitmapset * +ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate) +{ + /* see ExecGetInsertedCols() */ + if (relinfo->ri_RangeTableIndex != 0) + { + RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate); + + return rte->updatedCols; + } + else + { + ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo; + RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate); + PartitionRoutingInfo *partrouteinfo = relinfo->ri_PartitionInfo; + + if (partrouteinfo->pi_RootToPartitionMap != NULL) + return execute_attr_map_cols(partrouteinfo->pi_RootToPartitionMap->attrMap, + rte->updatedCols); + else + return rte->updatedCols; + } +} + +/* Return a bitmap representing generated columns being updated */ +Bitmapset * +ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate) +{ + /* see ExecGetInsertedCols() */ + if (relinfo->ri_RangeTableIndex != 0) + { + RangeTblEntry *rte = exec_rt_fetch(relinfo->ri_RangeTableIndex, estate); + + return rte->extraUpdatedCols; + } + else + { + ResultRelInfo *rootRelInfo = relinfo->ri_RootResultRelInfo; + RangeTblEntry *rte = exec_rt_fetch(rootRelInfo->ri_RangeTableIndex, estate); + PartitionRoutingInfo *partrouteinfo = relinfo->ri_PartitionInfo; + + if (partrouteinfo->pi_RootToPartitionMap != NULL) + return execute_attr_map_cols(partrouteinfo->pi_RootToPartitionMap->attrMap, + rte->extraUpdatedCols); + else + return rte->extraUpdatedCols; + } +} + +/* Return columns being updated, including generated columns */ +Bitmapset * +ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate) +{ + return bms_union(ExecGetUpdatedCols(relinfo, estate), + ExecGetExtraUpdatedCols(relinfo, estate)); +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index f450e4d80ad..9572ef0690e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -286,7 +286,7 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype if (cmdtype == CMD_UPDATE && !(rel->trigdesc && rel->trigdesc->trig_update_before_row) && !bms_is_member(i + 1 - FirstLowInvalidHeapAttributeNumber, - exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, estate)->extraUpdatedCols)) + ExecGetExtraUpdatedCols(resultRelInfo, estate))) { resultRelInfo->ri_GeneratedExprs[i] = NULL; continue; @@ -492,7 +492,7 @@ ExecInsert(ModifyTableState *mtstate, * if there's no BR trigger defined on the partition. */ if (resultRelInfo->ri_PartitionCheck && - (resultRelInfo->ri_PartitionRoot == NULL || + (resultRelInfo->ri_RootResultRelInfo == NULL || (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row))) ExecPartitionCheck(resultRelInfo, slot, estate, true); diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h index afd5801e6ab..08619609311 100644 --- a/src/include/access/tupconvert.h +++ b/src/include/access/tupconvert.h @@ -18,6 +18,7 @@ #include "access/htup.h" #include "access/tupdesc.h" #include "executor/tuptable.h" +#include "nodes/bitmapset.h" typedef struct TupleConversionMap @@ -43,6 +44,7 @@ extern HeapTuple execute_attr_map_tuple(HeapTuple tuple, TupleConversionMap *map extern TupleTableSlot *execute_attr_map_slot(AttrMap *attrMap, TupleTableSlot *in_slot, TupleTableSlot *out_slot); +extern Bitmapset *execute_attr_map_cols(AttrMap *attrMap, Bitmapset *inbitmap); extern void free_conversion_map(TupleConversionMap *map); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 238b77494f4..45f4fd63bce 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -191,7 +191,7 @@ extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation) extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, - Relation partition_root, + ResultRelInfo *partition_root_rri, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern void ExecCleanUpTriggerState(EState *estate); @@ -571,6 +571,11 @@ extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relI extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo); extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo); +extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate); +extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate); +extern Bitmapset *ExecGetExtraUpdatedCols(ResultRelInfo *relinfo, EState *estate); +extern Bitmapset *ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate); + /* * prototypes from functions in execIndexing.c */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 015bfc0cb53..8e5ccb37c50 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -483,8 +483,13 @@ typedef struct ResultRelInfo /* partition check expression state */ ExprState *ri_PartitionCheckExpr; - /* relation descriptor for root partitioned table */ - Relation ri_PartitionRoot; + /* + * RootResultRelInfo gives the target relation mentioned in the query, if + * it's a partitioned table. It is not set if the target relation + * mentioned in the query is an inherited table, nor when tuple routing is + * not needed. + */ + struct ResultRelInfo *ri_RootResultRelInfo; /* Additional information specific to partition tuple routing */ struct PartitionRoutingInfo *ri_PartitionInfo; diff --git a/src/test/isolation/expected/tuplelock-partition.out b/src/test/isolation/expected/tuplelock-partition.out new file mode 100644 index 00000000000..dd6d37c577a --- /dev/null +++ b/src/test/isolation/expected/tuplelock-partition.out @@ -0,0 +1,20 @@ +Parsed test spec with 2 sessions + +starting permutation: s1b s1update_nokey s2locktuple s1c +step s1b: BEGIN; +step s1update_nokey: INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET col1 = 'x', col2 = 'y'; +step s2locktuple: SELECT * FROM parttab FOR KEY SHARE; +col1 key col2 + +a 1 b +step s1c: COMMIT; + +starting permutation: s1b s1update_key s2locktuple s1c +step s1b: BEGIN; +step s1update_key: INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET key=1; +step s2locktuple: SELECT * FROM parttab FOR KEY SHARE; +step s1c: COMMIT; +step s2locktuple: <... completed> +col1 key col2 + +a 1 b diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index a4144127add..9ec39730ebc 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -53,6 +53,7 @@ test: propagate-lock-delete test: tuplelock-conflict test: tuplelock-update test: tuplelock-upgrade-no-deadlock +test: tuplelock-partition test: freeze-the-dead test: nowait test: nowait-2 diff --git a/src/test/isolation/specs/tuplelock-partition.spec b/src/test/isolation/specs/tuplelock-partition.spec new file mode 100644 index 00000000000..9a585cb1615 --- /dev/null +++ b/src/test/isolation/specs/tuplelock-partition.spec @@ -0,0 +1,32 @@ +# Test tuple locking on INSERT ON CONFLICT UPDATE on a partitioned table. + +setup +{ + DROP TABLE IF EXISTS parttab; + CREATE TABLE parttab (col1 text, key INTEGER PRIMARY KEY, col2 text) PARTITION BY LIST (key); + CREATE TABLE parttab1 (key INTEGER PRIMARY KEY, col1 text, col2 text); + CREATE TABLE parttab2 (key INTEGER PRIMARY KEY, col1 text, col2 text); + ALTER TABLE parttab ATTACH PARTITION parttab1 FOR VALUES IN (1); + ALTER TABLE parttab ATTACH PARTITION parttab2 FOR VALUES IN (2); + INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b'); +} + +teardown +{ + DROP TABLE parttab; +} + +session "s1" +step "s1b" { BEGIN; } +step "s1update_nokey" { INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET col1 = 'x', col2 = 'y'; } +step "s1update_key" { INSERT INTO parttab (key, col1, col2) VALUES (1, 'a', 'b') ON CONFLICT (key) DO UPDATE SET key=1; } +step "s1c" { COMMIT; } + +session "s2" +step "s2locktuple" { SELECT * FROM parttab FOR KEY SHARE; } + +# INSERT ON CONFLICT UPDATE, performs an UPDATE on non-key columns +permutation "s1b" "s1update_nokey" "s2locktuple" "s1c" + +# INSERT ON CONFLICT UPDATE, performs an UPDATE on key column +permutation "s1b" "s1update_key" "s2locktuple" "s1c" diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 5e5f98ac68a..dac1d07e92b 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -616,6 +616,45 @@ ERROR: new row for relation "t1" violates check constraint "t1_c3_check" DETAIL: Failing row contains (c1, c3) = (1, 10). SET SESSION AUTHORIZATION regress_priv_user1; DROP TABLE t1; +-- check error reporting with column privs on a partitioned table +CREATE TABLE errtst(a text, b text NOT NULL, c text, secret1 text, secret2 text) PARTITION BY LIST (a); +CREATE TABLE errtst_part_1(secret2 text, c text, a text, b text NOT NULL, secret1 text); +CREATE TABLE errtst_part_2(secret1 text, secret2 text, a text, c text, b text NOT NULL); +ALTER TABLE errtst ATTACH PARTITION errtst_part_1 FOR VALUES IN ('aaa'); +ALTER TABLE errtst ATTACH PARTITION errtst_part_2 FOR VALUES IN ('aaaa'); +GRANT SELECT (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT UPDATE (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT INSERT (a, b, c) ON TABLE errtst TO regress_priv_user2; +INSERT INTO errtst_part_1 (a, b, c, secret1, secret2) +VALUES ('aaa', 'bbb', 'ccc', 'the body', 'is in the attic'); +SET SESSION AUTHORIZATION regress_priv_user2; +-- Perform a few updates that violate the NOT NULL constraint. Make sure +-- the error messages don't leak the secret fields. +-- simple insert. +INSERT INTO errtst (a, b) VALUES ('aaa', NULL); +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaa, null, null). +-- simple update. +UPDATE errtst SET b = NULL; +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (b) = (null). +-- partitioning key is updated, doesn't move the row. +UPDATE errtst SET a = 'aaa', b = NULL; +ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). +-- row is moved to another partition. +UPDATE errtst SET a = 'aaaa', b = NULL; +ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). +-- row is moved to another partition. This differs from the previous case in +-- that the new partition is excluded by constraint exclusion, so its +-- ResultRelInfo is not created at ExecInitModifyTable, but needs to be +-- constructed on the fly when the updated tuple is routed to it. +UPDATE errtst SET a = 'aaaa', b = NULL WHERE a = 'aaa'; +ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint +DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE errtst; -- test column-level privileges when involved with DELETE SET SESSION AUTHORIZATION regress_priv_user1; ALTER TABLE atest6 ADD COLUMN three integer; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index fff76e0bd08..407150baf3a 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -401,6 +401,44 @@ UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being mod SET SESSION AUTHORIZATION regress_priv_user1; DROP TABLE t1; +-- check error reporting with column privs on a partitioned table +CREATE TABLE errtst(a text, b text NOT NULL, c text, secret1 text, secret2 text) PARTITION BY LIST (a); +CREATE TABLE errtst_part_1(secret2 text, c text, a text, b text NOT NULL, secret1 text); +CREATE TABLE errtst_part_2(secret1 text, secret2 text, a text, c text, b text NOT NULL); + +ALTER TABLE errtst ATTACH PARTITION errtst_part_1 FOR VALUES IN ('aaa'); +ALTER TABLE errtst ATTACH PARTITION errtst_part_2 FOR VALUES IN ('aaaa'); + +GRANT SELECT (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT UPDATE (a, b, c) ON TABLE errtst TO regress_priv_user2; +GRANT INSERT (a, b, c) ON TABLE errtst TO regress_priv_user2; + +INSERT INTO errtst_part_1 (a, b, c, secret1, secret2) +VALUES ('aaa', 'bbb', 'ccc', 'the body', 'is in the attic'); + +SET SESSION AUTHORIZATION regress_priv_user2; + +-- Perform a few updates that violate the NOT NULL constraint. Make sure +-- the error messages don't leak the secret fields. + +-- simple insert. +INSERT INTO errtst (a, b) VALUES ('aaa', NULL); +-- simple update. +UPDATE errtst SET b = NULL; +-- partitioning key is updated, doesn't move the row. +UPDATE errtst SET a = 'aaa', b = NULL; +-- row is moved to another partition. +UPDATE errtst SET a = 'aaaa', b = NULL; + +-- row is moved to another partition. This differs from the previous case in +-- that the new partition is excluded by constraint exclusion, so its +-- ResultRelInfo is not created at ExecInitModifyTable, but needs to be +-- constructed on the fly when the updated tuple is routed to it. +UPDATE errtst SET a = 'aaaa', b = NULL WHERE a = 'aaa'; + +SET SESSION AUTHORIZATION regress_priv_user1; +DROP TABLE errtst; + -- test column-level privileges when involved with DELETE SET SESSION AUTHORIZATION regress_priv_user1; ALTER TABLE atest6 ADD COLUMN three integer;