=== Applying patches on top of PostgreSQL commit ID fcc0e27f45e2d8dc6046435908a48626c91d3d84 === /etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf. Wed Sep 16 14:28:33 UTC 2026 On branch cf/7238 nothing to commit, working tree clean === using 'git am' to apply patch ./v1-0001-Obey-EXEC_FLAG_SKIP_TRIGGERS-when-inserting-tempo.patch === Applying: Obey EXEC_FLAG_SKIP_TRIGGERS when inserting temporal leftovers Using index info to reconstruct a base tree... M src/backend/executor/nodeModifyTable.c Falling back to patching base and 3-way merge... Auto-merging src/backend/executor/nodeModifyTable.c CONFLICT (content): Merge conflict in src/backend/executor/nodeModifyTable.c error: Failed to merge in the changes. hint: Use 'git am --show-current-patch=diff' to see the failed patch Patch failed at 0001 Obey EXEC_FLAG_SKIP_TRIGGERS when inserting temporal leftovers When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". === using patch(1) to apply patch ./v1-0001-Obey-EXEC_FLAG_SKIP_TRIGGERS-when-inserting-tempo.patch === patching file src/backend/executor/nodeModifyTable.c Hunk #1 FAILED at 1436. Hunk #2 FAILED at 1610. 2 out of 2 hunks FAILED -- saving rejects to file src/backend/executor/nodeModifyTable.c.rej Removing src/backend/executor/nodeModifyTable.c.rej === using 'git apply' to apply patch ./v1-0001-Obey-EXEC_FLAG_SKIP_TRIGGERS-when-inserting-tempo.patch === Applied patch to 'src/backend/executor/nodeModifyTable.c' with conflicts. U src/backend/executor/nodeModifyTable.c diff --cc src/backend/executor/nodeModifyTable.c index 2632431ea05,355f681b0c8..00000000000 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@@ -1387,6 -1407,247 +1387,250 @@@ ExecInsert(ModifyTableContext *context } /* ---------------------------------------------------------------- ++<<<<<<< ours ++======= + * ExecForPortionOfLeftovers + * + * Insert tuples for the untouched portion of a row in a FOR + * PORTION OF UPDATE/DELETE + * ---------------------------------------------------------------- + */ + static void + ExecForPortionOfLeftovers(ModifyTableContext *context, + EState *estate, + ResultRelInfo *resultRelInfo, + ItemPointer tupleid) + { + ModifyTableState *mtstate = context->mtstate; + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf; + Datum oldRange; + TypeCacheEntry *typcache; + ForPortionOfState *fpoState; + TupleTableSlot *oldtupleSlot; + TupleTableSlot *leftoverSlot; + TupleConversionMap *map = NULL; + HeapTuple oldtuple = NULL; + CmdType oldOperation; + TransitionCaptureState *oldTcs; + FmgrInfo flinfo; + PgStat_FunctionCallUsage fcusage; + ReturnSetInfo rsi; + bool didInit = false; + bool shouldFree = false; + bool fire_triggers = !(estate->es_top_eflags & EXEC_FLAG_SKIP_TRIGGERS); + ResultRelInfo *rootRelInfo = mtstate->rootResultRelInfo; + bool partitionRouting = + rootRelInfo && + rootRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_TABLE; + + LOCAL_FCINFO(fcinfo, 2); + + fpoState = resultRelInfo->ri_forPortionOf; + oldtupleSlot = fpoState->fp_Existing; + leftoverSlot = fpoState->fp_Leftover; + + /* + * Get the old pre-UPDATE/DELETE tuple. We will use its range to compute + * untouched parts of history, and if necessary we will insert copies with + * truncated start/end times. + * + * We have already locked the tuple in ExecUpdate/ExecDelete, and it has + * passed EvalPlanQual. This ensures that concurrent updates in READ + * COMMITTED can't insert conflicting temporal leftovers. + * + * It does *not* protect against concurrent update/deletes overlooking + * each others' leftovers though. See our isolation tests for details + * about that and a viable workaround. + */ + if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny, oldtupleSlot)) + elog(ERROR, "failed to fetch tuple for FOR PORTION OF"); + + slot_getallattrs(oldtupleSlot); + + /* Get the old range of the record being updated/deleted. */ + if (oldtupleSlot->tts_isnull[fpoState->fp_rangeAttno - 1]) + elog(ERROR, "found a NULL range in a temporal table"); + oldRange = oldtupleSlot->tts_values[fpoState->fp_rangeAttno - 1]; + + /* + * Get the range's type cache entry. This is worth caching for the whole + * UPDATE/DELETE as range functions do. + */ + + typcache = fpoState->fp_leftoverstypcache; + if (typcache == NULL) + { + typcache = lookup_type_cache(forPortionOf->rangeType, 0); + fpoState->fp_leftoverstypcache = typcache; + } + + /* + * Get the ranges to the left/right of the targeted range. We call a SETOF + * support function and insert as many temporal leftovers as it gives us. + * Although rangetypes have 0/1/2 leftovers, multiranges have 0/1, and + * other types may have more. + */ + + fmgr_info(forPortionOf->withoutPortionProc, &flinfo); + rsi.type = T_ReturnSetInfo; + rsi.econtext = mtstate->ps.ps_ExprContext; + rsi.expectedDesc = NULL; + rsi.allowedModes = (int) (SFRM_ValuePerCall); + rsi.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsi.setResult = NULL; + rsi.setDesc = NULL; + + InitFunctionCallInfoData(*fcinfo, &flinfo, 2, InvalidOid, NULL, (Node *) &rsi); + fcinfo->args[0].value = oldRange; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = fpoState->fp_targetRange; + fcinfo->args[1].isnull = false; + + /* + * For partitioned tables, we must read leftovers with the tuple + * descriptor of the child table, but insert into the root table to enable + * tuple routing. So leftoverSlot is configured with the root's tuple + * descriptor. But for traditional table inheritance, we don't need tuple + * routing and just insert directly into the child table to preserve + * child-specific columns. In that case, leftoverSlot uses the child's + * (resultRelInfo) tuple descriptor. + */ + if (partitionRouting) + { + map = ExecGetChildToRootMap(resultRelInfo); + resultRelInfo = resultRelInfo->ri_RootResultRelInfo; + } + + /* + * Insert a leftover for each value returned by the without_portion helper + * function + */ + while (true) + { + Datum leftover; + + /* Call the function one time */ + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + rsi.isDone = ExprSingleResult; + leftover = FunctionCallInvoke(fcinfo); + + pgstat_end_function_usage(&fcusage, + rsi.isDone != ExprMultipleResult); + + if (rsi.returnMode != SFRM_ValuePerCall) + elog(ERROR, "without_portion function violated function call protocol"); + + /* Are we done? */ + if (rsi.isDone == ExprEndResult) + break; + + if (fcinfo->isnull) + elog(ERROR, "got a null from without_portion function"); + + /* + * Does the new Datum violate domain checks? Row-level CHECK + * constraints are validated by ExecInsert, so we don't need to do + * anything here for those. + */ + if (forPortionOf->isDomain) + domain_check(leftover, false, forPortionOf->rangeVar->vartype, NULL, NULL); + + if (!didInit) + { + /* + * Make a copy of the pre-UPDATE row. Then we'll overwrite the + * range column below. Only partitioned targets need conversion to + * the root table's format, because they reinsert through the root + * relation for tuple routing. + */ + if (map != NULL) + { + leftoverSlot = execute_attr_map_slot(map->attrMap, + oldtupleSlot, + leftoverSlot); + } + else + { + oldtuple = ExecFetchSlotHeapTuple(oldtupleSlot, false, &shouldFree); + ExecForceStoreHeapTuple(oldtuple, leftoverSlot, false); + } + + /* + * Save some mtstate things so we can restore them below. XXX: + * Should we create our own ModifyTableState instead? + */ + oldOperation = mtstate->operation; + mtstate->operation = CMD_INSERT; + oldTcs = mtstate->mt_transition_capture; + + didInit = true; + } + else + { + /* + * Re-copy the original row into leftoverSlot because ExecInsert + * might pass leftoverSlot to BEFORE ROW INSERT triggers, which + * can modify the slot contents. + */ + if (map != NULL) + execute_attr_map_slot(map->attrMap, oldtupleSlot, leftoverSlot); + else + ExecForceStoreHeapTuple(oldtuple, leftoverSlot, false); + } + + leftoverSlot->tts_values[resultRelInfo->ri_forPortionOf->fp_rangeAttno - 1] = leftover; + leftoverSlot->tts_isnull[resultRelInfo->ri_forPortionOf->fp_rangeAttno - 1] = false; + ExecMaterializeSlot(leftoverSlot); + + /* + * The standard says that each temporal leftover should execute its + * own INSERT statement, firing all statement and row triggers, but + * skipping insert permission checks. Therefore we give each insert + * its own transition table. If we just push & pop a new trigger level + * for each insert, we get exactly what we need. + * + * But we also must obey EXEC_FLAG_SKIP_TRIGGERS. Our own temporal + * foreign keys use FOR PORTION OF to implement CASCADE/SET NULL/SET + * DEFAULT, which may insert temporal leftovers on the referencing + * table, causing its INSERT triggers to fire. If they had their own + * trigger level, they would fire before the temporal foreign key + * finished its work, observing an invalid intermediate state. In + * particular we would get spurious foreign key failures: if the + * original ON UPDATE CASCADE foreign key action deleted two parts of + * history, then inserting leftovers from the first delete would fail, + * because its reference has disappeared. (Note that despite the name, + * EXEC_FLAG_SKIP_TRIGGERS doesn't *skip* the triggers, only change + * when they fire.) + * + * We have to make sure that the inserts don't add to the ROW_COUNT + * diagnostic or the command tag, so we pass false for canSetTag. + */ + if (fire_triggers) + AfterTriggerBeginQuery(); + ExecSetupTransitionCaptureState(mtstate, estate); + fireBSTriggers(mtstate); + ExecInsert(context, resultRelInfo, leftoverSlot, false, NULL, NULL); + fireASTriggers(mtstate); + if (fire_triggers) + AfterTriggerEndQuery(estate); + } + + if (didInit) + { + mtstate->operation = oldOperation; + mtstate->mt_transition_capture = oldTcs; + + if (shouldFree) + heap_freetuple(oldtuple); + } + } + + /* ---------------------------------------------------------------- ++>>>>>>> theirs * ExecBatchInsert * * Insert multiple tuples in an efficient way.