PostgreSQL Source Code  git master
nodeModifyTable.c File Reference
#include "postgres.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "rewrite/rewriteHandler.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/memutils.h"
#include "utils/rel.h"
Include dependency graph for nodeModifyTable.c:

Go to the source code of this file.

Data Structures

struct  MTTargetRelLookup
 
struct  ModifyTableContext
 
struct  UpdateContext
 
struct  GetEPQSlotArg
 

Macros

#define MT_NRELS_HASH   64
 

Typedefs

typedef struct MTTargetRelLookup MTTargetRelLookup
 
typedef struct ModifyTableContext ModifyTableContext
 
typedef struct UpdateContext UpdateContext
 

Functions

static void ExecBatchInsert (ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int numSlots, EState *estate, bool canSetTag)
 
static void ExecPendingInserts (EState *estate)
 
static void ExecCrossPartitionUpdateForeignKey (ModifyTableContext *context, ResultRelInfo *sourcePartInfo, ResultRelInfo *destPartInfo, ItemPointer tupleid, TupleTableSlot *oldslot, TupleTableSlot *newslot)
 
static bool ExecOnConflictUpdate (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer conflictTid, TupleTableSlot *excludedSlot, bool canSetTag, TupleTableSlot **returning)
 
static TupleTableSlotExecPrepareTupleRouting (ModifyTableState *mtstate, EState *estate, PartitionTupleRouting *proute, ResultRelInfo *targetRelInfo, TupleTableSlot *slot, ResultRelInfo **partRelInfo)
 
static TupleTableSlotExecMerge (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool canSetTag)
 
static void ExecInitMerge (ModifyTableState *mtstate, EState *estate)
 
static bool ExecMergeMatched (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool canSetTag)
 
static void ExecMergeNotMatched (ModifyTableContext *context, ResultRelInfo *resultRelInfo, bool canSetTag)
 
static void ExecCheckPlanOutput (Relation resultRel, List *targetList)
 
static TupleTableSlotExecProcessReturning (ResultRelInfo *resultRelInfo, TupleTableSlot *tupleSlot, TupleTableSlot *planSlot)
 
static void ExecCheckTupleVisible (EState *estate, Relation rel, TupleTableSlot *slot)
 
static void ExecCheckTIDVisible (EState *estate, ResultRelInfo *relinfo, ItemPointer tid, TupleTableSlot *tempSlot)
 
void ExecInitStoredGenerated (ResultRelInfo *resultRelInfo, EState *estate, CmdType cmdtype)
 
void ExecComputeStoredGenerated (ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, CmdType cmdtype)
 
static void ExecInitInsertProjection (ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
 
static void ExecInitUpdateProjection (ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
 
static TupleTableSlotExecGetInsertNewTuple (ResultRelInfo *relinfo, TupleTableSlot *planSlot)
 
TupleTableSlotExecGetUpdateNewTuple (ResultRelInfo *relinfo, TupleTableSlot *planSlot, TupleTableSlot *oldSlot)
 
static TupleTableSlotExecInsert (ModifyTableContext *context, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, bool canSetTag, TupleTableSlot **inserted_tuple, ResultRelInfo **insert_destrel)
 
static bool ExecDeletePrologue (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot **epqreturnslot, TM_Result *result)
 
static TupleTableSlotGetEPQSlot (void *arg)
 
static TM_Result ExecDeleteAct (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool changingPart, bool lockUpdated)
 
static void ExecDeleteEpilogue (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, bool changingPart)
 
static TupleTableSlotExecDelete (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, bool processReturning, bool changingPart, bool canSetTag, bool *tupleDeleted, TupleTableSlot **epqreturnslot)
 
static bool ExecCrossPartitionUpdate (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt, TupleTableSlot **retry_slot, TupleTableSlot **inserted_tuple, ResultRelInfo **insert_destrel)
 
static bool ExecUpdatePrologue (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, TM_Result *result)
 
static void ExecUpdatePrepareSlot (ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
 
static TM_Result ExecUpdateAct (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, bool lockUpdated, UpdateContext *updateCxt)
 
static void ExecUpdateEpilogue (ModifyTableContext *context, UpdateContext *updateCxt, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot)
 
static TupleTableSlotExecUpdate (ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, bool locked)
 
void ExecInitMergeTupleSlots (ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
 
static void fireBSTriggers (ModifyTableState *node)
 
static void fireASTriggers (ModifyTableState *node)
 
static void ExecSetupTransitionCaptureState (ModifyTableState *mtstate, EState *estate)
 
static TupleTableSlotExecModifyTable (PlanState *pstate)
 
ResultRelInfoExecLookupResultRelByOid (ModifyTableState *node, Oid resultoid, bool missing_ok, bool update_cache)
 
ModifyTableStateExecInitModifyTable (ModifyTable *node, EState *estate, int eflags)
 
void ExecEndModifyTable (ModifyTableState *node)
 
void ExecReScanModifyTable (ModifyTableState *node)
 

Macro Definition Documentation

◆ MT_NRELS_HASH

#define MT_NRELS_HASH   64

Typedef Documentation

◆ ModifyTableContext

◆ MTTargetRelLookup

◆ UpdateContext

typedef struct UpdateContext UpdateContext

Function Documentation

◆ ExecBatchInsert()

static void ExecBatchInsert ( ModifyTableState mtstate,
ResultRelInfo resultRelInfo,
TupleTableSlot **  slots,
TupleTableSlot **  planSlots,
int  numSlots,
EState estate,
bool  canSetTag 
)
static

Definition at line 1218 of file nodeModifyTable.c.

1225 {
1226  int i;
1227  int numInserted = numSlots;
1228  TupleTableSlot *slot = NULL;
1229  TupleTableSlot **rslots;
1230 
1231  /*
1232  * insert into foreign table: let the FDW do it
1233  */
1234  rslots = resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert(estate,
1235  resultRelInfo,
1236  slots,
1237  planSlots,
1238  &numInserted);
1239 
1240  for (i = 0; i < numInserted; i++)
1241  {
1242  slot = rslots[i];
1243 
1244  /*
1245  * AFTER ROW Triggers might reference the tableoid column, so
1246  * (re-)initialize tts_tableOid before evaluating them.
1247  */
1248  slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
1249 
1250  /* AFTER ROW INSERT Triggers */
1251  ExecARInsertTriggers(estate, resultRelInfo, slot, NIL,
1252  mtstate->mt_transition_capture);
1253 
1254  /*
1255  * Check any WITH CHECK OPTION constraints from parent views. See the
1256  * comment in ExecInsert.
1257  */
1258  if (resultRelInfo->ri_WithCheckOptions != NIL)
1259  ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
1260  }
1261 
1262  if (canSetTag && numInserted > 0)
1263  estate->es_processed += numInserted;
1264 }
void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
Definition: execMain.c:2038
int i
Definition: isn.c:73
@ WCO_VIEW_CHECK
Definition: parsenodes.h:1314
#define NIL
Definition: pg_list.h:68
#define RelationGetRelid(relation)
Definition: rel.h:503
uint64 es_processed
Definition: execnodes.h:664
ExecForeignBatchInsert_function ExecForeignBatchInsert
Definition: fdwapi.h:233
struct TransitionCaptureState * mt_transition_capture
Definition: execnodes.h:1301
Relation ri_RelationDesc
Definition: execnodes.h:450
List * ri_WithCheckOptions
Definition: execnodes.h:513
struct FdwRoutine * ri_FdwRoutine
Definition: execnodes.h:497
Oid tts_tableOid
Definition: tuptable.h:131
void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot, List *recheckIndexes, TransitionCaptureState *transition_capture)
Definition: trigger.c:2545

References EState::es_processed, ExecARInsertTriggers(), FdwRoutine::ExecForeignBatchInsert, ExecWithCheckOptions(), i, ModifyTableState::mt_transition_capture, NIL, RelationGetRelid, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_WithCheckOptions, TupleTableSlot::tts_tableOid, and WCO_VIEW_CHECK.

Referenced by ExecInsert(), and ExecPendingInserts().

◆ ExecCheckPlanOutput()

static void ExecCheckPlanOutput ( Relation  resultRel,
List targetList 
)
static

Definition at line 181 of file nodeModifyTable.c.

182 {
183  TupleDesc resultDesc = RelationGetDescr(resultRel);
184  int attno = 0;
185  ListCell *lc;
186 
187  foreach(lc, targetList)
188  {
189  TargetEntry *tle = (TargetEntry *) lfirst(lc);
190  Form_pg_attribute attr;
191 
192  Assert(!tle->resjunk); /* caller removed junk items already */
193 
194  if (attno >= resultDesc->natts)
195  ereport(ERROR,
196  (errcode(ERRCODE_DATATYPE_MISMATCH),
197  errmsg("table row type and query-specified row type do not match"),
198  errdetail("Query has too many columns.")));
199  attr = TupleDescAttr(resultDesc, attno);
200  attno++;
201 
202  if (!attr->attisdropped)
203  {
204  /* Normal case: demand type match */
205  if (exprType((Node *) tle->expr) != attr->atttypid)
206  ereport(ERROR,
207  (errcode(ERRCODE_DATATYPE_MISMATCH),
208  errmsg("table row type and query-specified row type do not match"),
209  errdetail("Table has type %s at ordinal position %d, but query expects %s.",
210  format_type_be(attr->atttypid),
211  attno,
212  format_type_be(exprType((Node *) tle->expr)))));
213  }
214  else
215  {
216  /*
217  * For a dropped column, we can't check atttypid (it's likely 0).
218  * In any case the planner has most likely inserted an INT4 null.
219  * What we insist on is just *some* NULL constant.
220  */
221  if (!IsA(tle->expr, Const) ||
222  !((Const *) tle->expr)->constisnull)
223  ereport(ERROR,
224  (errcode(ERRCODE_DATATYPE_MISMATCH),
225  errmsg("table row type and query-specified row type do not match"),
226  errdetail("Query provides a value for a dropped column at ordinal position %d.",
227  attno)));
228  }
229  }
230  if (attno != resultDesc->natts)
231  ereport(ERROR,
232  (errcode(ERRCODE_DATATYPE_MISMATCH),
233  errmsg("table row type and query-specified row type do not match"),
234  errdetail("Query has too few columns.")));
235 }
int errdetail(const char *fmt,...)
Definition: elog.c:1202
int errcode(int sqlerrcode)
Definition: elog.c:858
int errmsg(const char *fmt,...)
Definition: elog.c:1069
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
char * format_type_be(Oid type_oid)
Definition: format_type.c:339
Assert(fmt[strlen(fmt) - 1] !='\n')
Oid exprType(const Node *expr)
Definition: nodeFuncs.c:43
#define IsA(nodeptr, _type_)
Definition: nodes.h:179
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:207
#define lfirst(lc)
Definition: pg_list.h:172
#define RelationGetDescr(relation)
Definition: rel.h:529
Definition: nodes.h:129
Expr * expr
Definition: primnodes.h:1731
#define TupleDescAttr(tupdesc, i)
Definition: tupdesc.h:92

References Assert(), ereport, errcode(), errdetail(), errmsg(), ERROR, TargetEntry::expr, exprType(), format_type_be(), IsA, lfirst, TupleDescData::natts, RelationGetDescr, and TupleDescAttr.

Referenced by ExecInitInsertProjection(), and ExecInitMerge().

◆ ExecCheckTIDVisible()

static void ExecCheckTIDVisible ( EState estate,
ResultRelInfo relinfo,
ItemPointer  tid,
TupleTableSlot tempSlot 
)
static

Definition at line 316 of file nodeModifyTable.c.

320 {
321  Relation rel = relinfo->ri_RelationDesc;
322 
323  /* Redundantly check isolation level */
325  return;
326 
327  if (!table_tuple_fetch_row_version(rel, tid, SnapshotAny, tempSlot))
328  elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
329  ExecCheckTupleVisible(estate, rel, tempSlot);
330  ExecClearTuple(tempSlot);
331 }
static void ExecCheckTupleVisible(EState *estate, Relation rel, TupleTableSlot *slot)
#define SnapshotAny
Definition: snapmgr.h:67
static bool table_tuple_fetch_row_version(Relation rel, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot)
Definition: tableam.h:1285
static TupleTableSlot * ExecClearTuple(TupleTableSlot *slot)
Definition: tuptable.h:471
#define IsolationUsesXactSnapshot()
Definition: xact.h:51

References elog(), ERROR, ExecCheckTupleVisible(), ExecClearTuple(), IsolationUsesXactSnapshot, ResultRelInfo::ri_RelationDesc, SnapshotAny, and table_tuple_fetch_row_version().

Referenced by ExecInsert().

◆ ExecCheckTupleVisible()

static void ExecCheckTupleVisible ( EState estate,
Relation  rel,
TupleTableSlot slot 
)
static

Definition at line 282 of file nodeModifyTable.c.

285 {
287  return;
288 
289  if (!table_tuple_satisfies_snapshot(rel, slot, estate->es_snapshot))
290  {
291  Datum xminDatum;
292  TransactionId xmin;
293  bool isnull;
294 
295  xminDatum = slot_getsysattr(slot, MinTransactionIdAttributeNumber, &isnull);
296  Assert(!isnull);
297  xmin = DatumGetTransactionId(xminDatum);
298 
299  /*
300  * We should not raise a serialization failure if the conflict is
301  * against a tuple inserted by our own transaction, even if it's not
302  * visible to our snapshot. (This would happen, for example, if
303  * conflicting keys are proposed for insertion in a single command.)
304  */
306  ereport(ERROR,
308  errmsg("could not serialize access due to concurrent update")));
309  }
310 }
uint32 TransactionId
Definition: c.h:636
#define ERRCODE_T_R_SERIALIZATION_FAILURE
Definition: pgbench.c:76
uintptr_t Datum
Definition: postgres.h:64
static TransactionId DatumGetTransactionId(Datum X)
Definition: postgres.h:262
Snapshot es_snapshot
Definition: execnodes.h:616
#define MinTransactionIdAttributeNumber
Definition: sysattr.h:22
static bool table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snapshot)
Definition: tableam.h:1332
static Datum slot_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
Definition: tuptable.h:448
bool TransactionIdIsCurrentTransactionId(TransactionId xid)
Definition: xact.c:926

References Assert(), DatumGetTransactionId(), ereport, errcode(), ERRCODE_T_R_SERIALIZATION_FAILURE, errmsg(), ERROR, EState::es_snapshot, IsolationUsesXactSnapshot, MinTransactionIdAttributeNumber, slot_getsysattr(), table_tuple_satisfies_snapshot(), and TransactionIdIsCurrentTransactionId().

Referenced by ExecCheckTIDVisible(), and ExecOnConflictUpdate().

◆ ExecComputeStoredGenerated()

void ExecComputeStoredGenerated ( ResultRelInfo resultRelInfo,
EState estate,
TupleTableSlot slot,
CmdType  cmdtype 
)

Definition at line 446 of file nodeModifyTable.c.

449 {
450  Relation rel = resultRelInfo->ri_RelationDesc;
451  TupleDesc tupdesc = RelationGetDescr(rel);
452  int natts = tupdesc->natts;
453  ExprContext *econtext = GetPerTupleExprContext(estate);
454  ExprState **ri_GeneratedExprs;
455  MemoryContext oldContext;
456  Datum *values;
457  bool *nulls;
458 
459  /* We should not be called unless this is true */
460  Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
461 
462  /*
463  * Initialize the expressions if we didn't already, and check whether we
464  * can exit early because nothing needs to be computed.
465  */
466  if (cmdtype == CMD_UPDATE)
467  {
468  if (resultRelInfo->ri_GeneratedExprsU == NULL)
469  ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
470  if (resultRelInfo->ri_NumGeneratedNeededU == 0)
471  return;
472  ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsU;
473  }
474  else
475  {
476  if (resultRelInfo->ri_GeneratedExprsI == NULL)
477  ExecInitStoredGenerated(resultRelInfo, estate, cmdtype);
478  /* Early exit is impossible given the prior Assert */
479  Assert(resultRelInfo->ri_NumGeneratedNeededI > 0);
480  ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsI;
481  }
482 
483  oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
484 
485  values = palloc(sizeof(*values) * natts);
486  nulls = palloc(sizeof(*nulls) * natts);
487 
488  slot_getallattrs(slot);
489  memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
490 
491  for (int i = 0; i < natts; i++)
492  {
493  Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
494 
495  if (ri_GeneratedExprs[i])
496  {
497  Datum val;
498  bool isnull;
499 
500  Assert(attr->attgenerated == ATTRIBUTE_GENERATED_STORED);
501 
502  econtext->ecxt_scantuple = slot;
503 
504  val = ExecEvalExpr(ri_GeneratedExprs[i], econtext, &isnull);
505 
506  /*
507  * We must make a copy of val as we have no guarantees about where
508  * memory for a pass-by-reference Datum is located.
509  */
510  if (!isnull)
511  val = datumCopy(val, attr->attbyval, attr->attlen);
512 
513  values[i] = val;
514  nulls[i] = isnull;
515  }
516  else
517  {
518  if (!nulls[i])
519  values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
520  }
521  }
522 
523  ExecClearTuple(slot);
524  memcpy(slot->tts_values, values, sizeof(*values) * natts);
525  memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
526  ExecStoreVirtualTuple(slot);
527  ExecMaterializeSlot(slot);
528 
529  MemoryContextSwitchTo(oldContext);
530 }
static Datum values[MAXATTR]
Definition: bootstrap.c:156
Datum datumCopy(Datum value, bool typByVal, int typLen)
Definition: datum.c:132
TupleTableSlot * ExecStoreVirtualTuple(TupleTableSlot *slot)
Definition: execTuples.c:1552
#define GetPerTupleExprContext(estate)
Definition: executor.h:548
#define GetPerTupleMemoryContext(estate)
Definition: executor.h:553
static Datum ExecEvalExpr(ExprState *state, ExprContext *econtext, bool *isNull)
Definition: executor.h:331
long val
Definition: informix.c:664
void * palloc(Size size)
Definition: mcxt.c:1210
void ExecInitStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, CmdType cmdtype)
@ CMD_UPDATE
Definition: nodes.h:277
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:138
TupleTableSlot * ecxt_scantuple
Definition: execnodes.h:249
ExprState ** ri_GeneratedExprsI
Definition: execnodes.h:522
int ri_NumGeneratedNeededU
Definition: execnodes.h:527
ExprState ** ri_GeneratedExprsU
Definition: execnodes.h:523
int ri_NumGeneratedNeededI
Definition: execnodes.h:526
bool has_generated_stored
Definition: tupdesc.h:45
TupleConstr * constr
Definition: tupdesc.h:85
bool * tts_isnull
Definition: tuptable.h:128
Datum * tts_values
Definition: tuptable.h:126
static void slot_getallattrs(TupleTableSlot *slot)
Definition: tuptable.h:400
static void ExecMaterializeSlot(TupleTableSlot *slot)
Definition: tuptable.h:489

References Assert(), CMD_UPDATE, TupleDescData::constr, datumCopy(), ExprContext::ecxt_scantuple, ExecClearTuple(), ExecEvalExpr(), ExecInitStoredGenerated(), ExecMaterializeSlot(), ExecStoreVirtualTuple(), GetPerTupleExprContext, GetPerTupleMemoryContext, TupleConstr::has_generated_stored, i, MemoryContextSwitchTo(), TupleDescData::natts, palloc(), RelationGetDescr, ResultRelInfo::ri_GeneratedExprsI, ResultRelInfo::ri_GeneratedExprsU, ResultRelInfo::ri_NumGeneratedNeededI, ResultRelInfo::ri_NumGeneratedNeededU, ResultRelInfo::ri_RelationDesc, slot_getallattrs(), TupleTableSlot::tts_isnull, TupleTableSlot::tts_values, TupleDescAttr, val, and values.

Referenced by CopyFrom(), ExecInsert(), ExecSimpleRelationInsert(), ExecSimpleRelationUpdate(), and ExecUpdatePrepareSlot().

◆ ExecCrossPartitionUpdate()

static bool ExecCrossPartitionUpdate ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot slot,
bool  canSetTag,
UpdateContext updateCxt,
TupleTableSlot **  retry_slot,
TupleTableSlot **  inserted_tuple,
ResultRelInfo **  insert_destrel 
)
static

Definition at line 1729 of file nodeModifyTable.c.

1738 {
1739  ModifyTableState *mtstate = context->mtstate;
1740  EState *estate = mtstate->ps.state;
1741  TupleConversionMap *tupconv_map;
1742  bool tuple_deleted;
1743  TupleTableSlot *epqslot = NULL;
1744 
1745  context->cpUpdateReturningSlot = NULL;
1746  *retry_slot = NULL;
1747 
1748  /*
1749  * Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row
1750  * to migrate to a different partition. Maybe this can be implemented
1751  * some day, but it seems a fringe feature with little redeeming value.
1752  */
1753  if (((ModifyTable *) mtstate->ps.plan)->onConflictAction == ONCONFLICT_UPDATE)
1754  ereport(ERROR,
1755  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1756  errmsg("invalid ON UPDATE specification"),
1757  errdetail("The result tuple would appear in a different partition than the original tuple.")));
1758 
1759  /*
1760  * When an UPDATE is run directly on a leaf partition, simply fail with a
1761  * partition constraint violation error.
1762  */
1763  if (resultRelInfo == mtstate->rootResultRelInfo)
1764  ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
1765 
1766  /* Initialize tuple routing info if not already done. */
1767  if (mtstate->mt_partition_tuple_routing == NULL)
1768  {
1769  Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
1770  MemoryContext oldcxt;
1771 
1772  /* Things built here have to last for the query duration. */
1773  oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
1774 
1775  mtstate->mt_partition_tuple_routing =
1776  ExecSetupPartitionTupleRouting(estate, rootRel);
1777 
1778  /*
1779  * Before a partition's tuple can be re-routed, it must first be
1780  * converted to the root's format, so we'll need a slot for storing
1781  * such tuples.
1782  */
1783  Assert(mtstate->mt_root_tuple_slot == NULL);
1784  mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
1785 
1786  MemoryContextSwitchTo(oldcxt);
1787  }
1788 
1789  /*
1790  * Row movement, part 1. Delete the tuple, but skip RETURNING processing.
1791  * We want to return rows from INSERT.
1792  */
1793  ExecDelete(context, resultRelInfo,
1794  tupleid, oldtuple,
1795  false, /* processReturning */
1796  true, /* changingPart */
1797  false, /* canSetTag */
1798  &tuple_deleted, &epqslot);
1799 
1800  /*
1801  * For some reason if DELETE didn't happen (e.g. trigger prevented it, or
1802  * it was already deleted by self, or it was concurrently deleted by
1803  * another transaction), then we should skip the insert as well;
1804  * otherwise, an UPDATE could cause an increase in the total number of
1805  * rows across all partitions, which is clearly wrong.
1806  *
1807  * For a normal UPDATE, the case where the tuple has been the subject of a
1808  * concurrent UPDATE or DELETE would be handled by the EvalPlanQual
1809  * machinery, but for an UPDATE that we've translated into a DELETE from
1810  * this partition and an INSERT into some other partition, that's not
1811  * available, because CTID chains can't span relation boundaries. We
1812  * mimic the semantics to a limited extent by skipping the INSERT if the
1813  * DELETE fails to find a tuple. This ensures that two concurrent
1814  * attempts to UPDATE the same tuple at the same time can't turn one tuple
1815  * into two, and that an UPDATE of a just-deleted tuple can't resurrect
1816  * it.
1817  */
1818  if (!tuple_deleted)
1819  {
1820  /*
1821  * epqslot will be typically NULL. But when ExecDelete() finds that
1822  * another transaction has concurrently updated the same row, it
1823  * re-fetches the row, skips the delete, and epqslot is set to the
1824  * re-fetched tuple slot. In that case, we need to do all the checks
1825  * again. For MERGE, we leave everything to the caller (it must do
1826  * additional rechecking, and might end up executing a different
1827  * action entirely).
1828  */
1829  if (context->relaction != NULL)
1830  return false;
1831  else if (TupIsNull(epqslot))
1832  return true;
1833  else
1834  {
1835  /* Fetch the most recent version of old tuple. */
1836  TupleTableSlot *oldSlot;
1837 
1838  /* ... but first, make sure ri_oldTupleSlot is initialized. */
1839  if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
1840  ExecInitUpdateProjection(mtstate, resultRelInfo);
1841  oldSlot = resultRelInfo->ri_oldTupleSlot;
1842  if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
1843  tupleid,
1844  SnapshotAny,
1845  oldSlot))
1846  elog(ERROR, "failed to fetch tuple being updated");
1847  /* and project the new tuple to retry the UPDATE with */
1848  *retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
1849  oldSlot);
1850  return false;
1851  }
1852  }
1853 
1854  /*
1855  * resultRelInfo is one of the per-relation resultRelInfos. So we should
1856  * convert the tuple into root's tuple descriptor if needed, since
1857  * ExecInsert() starts the search from root.
1858  */
1859  tupconv_map = ExecGetChildToRootMap(resultRelInfo);
1860  if (tupconv_map != NULL)
1861  slot = execute_attr_map_slot(tupconv_map->attrMap,
1862  slot,
1863  mtstate->mt_root_tuple_slot);
1864 
1865  /* Tuple routing starts from the root table. */
1866  context->cpUpdateReturningSlot =
1867  ExecInsert(context, mtstate->rootResultRelInfo, slot, canSetTag,
1868  inserted_tuple, insert_destrel);
1869 
1870  /*
1871  * Reset the transition state that may possibly have been written by
1872  * INSERT.
1873  */
1874  if (mtstate->mt_transition_capture)
1876 
1877  /* We're done moving. */
1878  return true;
1879 }
#define unlikely(x)
Definition: c.h:295
void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
Definition: execMain.c:1832
PartitionTupleRouting * ExecSetupPartitionTupleRouting(EState *estate, Relation rel)
TupleConversionMap * ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
Definition: execUtils.c:1237
static TupleTableSlot * ExecInsert(ModifyTableContext *context, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, bool canSetTag, TupleTableSlot **inserted_tuple, ResultRelInfo **insert_destrel)
TupleTableSlot * ExecGetUpdateNewTuple(ResultRelInfo *relinfo, TupleTableSlot *planSlot, TupleTableSlot *oldSlot)
static void ExecInitUpdateProjection(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
static TupleTableSlot * ExecDelete(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, bool processReturning, bool changingPart, bool canSetTag, bool *tupleDeleted, TupleTableSlot **epqreturnslot)
@ ONCONFLICT_UPDATE
Definition: nodes.h:428
MemoryContext es_query_cxt
Definition: execnodes.h:660
MergeActionState * relaction
TupleTableSlot * cpUpdateReturningSlot
ModifyTableState * mtstate
struct PartitionTupleRouting * mt_partition_tuple_routing
Definition: execnodes.h:1298
TupleTableSlot * mt_root_tuple_slot
Definition: execnodes.h:1295
PlanState ps
Definition: execnodes.h:1262
ResultRelInfo * rootResultRelInfo
Definition: execnodes.h:1275
Plan * plan
Definition: execnodes.h:1035
EState * state
Definition: execnodes.h:1037
bool ri_projectNewInfoValid
Definition: execnodes.h:477
TupleTableSlot * ri_oldTupleSlot
Definition: execnodes.h:475
TupleTableSlot * tcs_original_insert_tuple
Definition: trigger.h:76
AttrMap * attrMap
Definition: tupconvert.h:28
TupleTableSlot * table_slot_create(Relation relation, List **reglist)
Definition: tableam.c:91
TupleTableSlot * execute_attr_map_slot(AttrMap *attrMap, TupleTableSlot *in_slot, TupleTableSlot *out_slot)
Definition: tupconvert.c:192
#define TupIsNull(slot)
Definition: tuptable.h:300

References Assert(), TupleConversionMap::attrMap, ModifyTableContext::cpUpdateReturningSlot, elog(), ereport, errcode(), errdetail(), errmsg(), ERROR, EState::es_query_cxt, ExecDelete(), ExecGetChildToRootMap(), ExecGetUpdateNewTuple(), ExecInitUpdateProjection(), ExecInsert(), ExecPartitionCheckEmitError(), ExecSetupPartitionTupleRouting(), execute_attr_map_slot(), MemoryContextSwitchTo(), ModifyTableState::mt_partition_tuple_routing, ModifyTableState::mt_root_tuple_slot, ModifyTableState::mt_transition_capture, ModifyTableContext::mtstate, ONCONFLICT_UPDATE, PlanState::plan, ModifyTableState::ps, ModifyTableContext::relaction, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_RelationDesc, ModifyTableState::rootResultRelInfo, SnapshotAny, PlanState::state, table_slot_create(), table_tuple_fetch_row_version(), TransitionCaptureState::tcs_original_insert_tuple, TupIsNull, and unlikely.

Referenced by ExecUpdateAct().

◆ ExecCrossPartitionUpdateForeignKey()

static void ExecCrossPartitionUpdateForeignKey ( ModifyTableContext context,
ResultRelInfo sourcePartInfo,
ResultRelInfo destPartInfo,
ItemPointer  tupleid,
TupleTableSlot oldslot,
TupleTableSlot newslot 
)
static

Definition at line 2179 of file nodeModifyTable.c.

2185 {
2186  ListCell *lc;
2187  ResultRelInfo *rootRelInfo;
2188  List *ancestorRels;
2189 
2190  rootRelInfo = sourcePartInfo->ri_RootResultRelInfo;
2191  ancestorRels = ExecGetAncestorResultRels(context->estate, sourcePartInfo);
2192 
2193  /*
2194  * For any foreign keys that point directly into a non-root ancestors of
2195  * the source partition, we can in theory fire an update event to enforce
2196  * those constraints using their triggers, if we could tell that both the
2197  * source and the destination partitions are under the same ancestor. But
2198  * for now, we simply report an error that those cannot be enforced.
2199  */
2200  foreach(lc, ancestorRels)
2201  {
2202  ResultRelInfo *rInfo = lfirst(lc);
2203  TriggerDesc *trigdesc = rInfo->ri_TrigDesc;
2204  bool has_noncloned_fkey = false;
2205 
2206  /* Root ancestor's triggers will be processed. */
2207  if (rInfo == rootRelInfo)
2208  continue;
2209 
2210  if (trigdesc && trigdesc->trig_update_after_row)
2211  {
2212  for (int i = 0; i < trigdesc->numtriggers; i++)
2213  {
2214  Trigger *trig = &trigdesc->triggers[i];
2215 
2216  if (!trig->tgisclone &&
2218  {
2219  has_noncloned_fkey = true;
2220  break;
2221  }
2222  }
2223  }
2224 
2225  if (has_noncloned_fkey)
2226  ereport(ERROR,
2227  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2228  errmsg("cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key"),
2229  errdetail("A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\".",
2232  errhint("Consider defining the foreign key on table \"%s\".",
2233  RelationGetRelationName(rootRelInfo->ri_RelationDesc))));
2234  }
2235 
2236  /* Perform the root table's triggers. */
2237  ExecARUpdateTriggers(context->estate,
2238  rootRelInfo, sourcePartInfo, destPartInfo,
2239  tupleid, NULL, newslot, NIL, NULL, true);
2240 }
int errhint(const char *fmt,...)
Definition: elog.c:1316
List * ExecGetAncestorResultRels(EState *estate, ResultRelInfo *resultRelInfo)
Definition: execMain.c:1359
#define RelationGetRelationName(relation)
Definition: rel.h:537
int RI_FKey_trigger_type(Oid tgfoid)
Definition: ri_triggers.c:3004
Definition: pg_list.h:54
struct ResultRelInfo * ri_RootResultRelInfo
Definition: execnodes.h:574
TriggerDesc * ri_TrigDesc
Definition: execnodes.h:480
int numtriggers
Definition: reltrigger.h:50
Trigger * triggers
Definition: reltrigger.h:49
bool trig_update_after_row
Definition: reltrigger.h:62
Oid tgfoid
Definition: reltrigger.h:28
bool tgisclone
Definition: reltrigger.h:32
void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ResultRelInfo *src_partinfo, ResultRelInfo *dst_partinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot *newslot, List *recheckIndexes, TransitionCaptureState *transition_capture, bool is_crosspart_update)
Definition: trigger.c:3090
#define RI_TRIGGER_PK
Definition: trigger.h:282

References ereport, errcode(), errdetail(), errhint(), errmsg(), ERROR, ModifyTableContext::estate, ExecARUpdateTriggers(), ExecGetAncestorResultRels(), i, lfirst, NIL, TriggerDesc::numtriggers, RelationGetRelationName, RI_FKey_trigger_type(), ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_RootResultRelInfo, ResultRelInfo::ri_TrigDesc, RI_TRIGGER_PK, Trigger::tgfoid, Trigger::tgisclone, TriggerDesc::trig_update_after_row, and TriggerDesc::triggers.

Referenced by ExecUpdateAct().

◆ ExecDelete()

static TupleTableSlot* ExecDelete ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
bool  processReturning,
bool  changingPart,
bool  canSetTag,
bool tupleDeleted,
TupleTableSlot **  epqreturnslot 
)
static

Definition at line 1451 of file nodeModifyTable.c.

1460 {
1461  EState *estate = context->estate;
1462  Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
1463  TupleTableSlot *slot = NULL;
1464  TM_Result result;
1465 
1466  if (tupleDeleted)
1467  *tupleDeleted = false;
1468 
1469  /*
1470  * Prepare for the delete. This includes BEFORE ROW triggers, so we're
1471  * done if it says we are.
1472  */
1473  if (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,
1474  epqreturnslot, NULL))
1475  return NULL;
1476 
1477  /* INSTEAD OF ROW DELETE Triggers */
1478  if (resultRelInfo->ri_TrigDesc &&
1479  resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
1480  {
1481  bool dodelete;
1482 
1483  Assert(oldtuple != NULL);
1484  dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);
1485 
1486  if (!dodelete) /* "do nothing" */
1487  return NULL;
1488  }
1489  else if (resultRelInfo->ri_FdwRoutine)
1490  {
1491  /*
1492  * delete from foreign table: let the FDW do it
1493  *
1494  * We offer the returning slot as a place to store RETURNING data,
1495  * although the FDW can return some other slot if it wants.
1496  */
1497  slot = ExecGetReturningSlot(estate, resultRelInfo);
1498  slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
1499  resultRelInfo,
1500  slot,
1501  context->planSlot);
1502 
1503  if (slot == NULL) /* "do nothing" */
1504  return NULL;
1505 
1506  /*
1507  * RETURNING expressions might reference the tableoid column, so
1508  * (re)initialize tts_tableOid before evaluating them.
1509  */
1510  if (TTS_EMPTY(slot))
1511  ExecStoreAllNullTuple(slot);
1512 
1513  slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
1514  }
1515  else
1516  {
1517  /*
1518  * delete the tuple
1519  *
1520  * Note: if context->estate->es_crosscheck_snapshot isn't
1521  * InvalidSnapshot, we check that the row to be deleted is visible to
1522  * that snapshot, and throw a can't-serialize error if not. This is a
1523  * special-case behavior needed for referential integrity updates in
1524  * transaction-snapshot mode transactions.
1525  */
1526 ldelete:
1527  result = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart,
1529 
1530  switch (result)
1531  {
1532  case TM_SelfModified:
1533 
1534  /*
1535  * The target tuple was already updated or deleted by the
1536  * current command, or by a later command in the current
1537  * transaction. The former case is possible in a join DELETE
1538  * where multiple tuples join to the same target tuple. This
1539  * is somewhat questionable, but Postgres has always allowed
1540  * it: we just ignore additional deletion attempts.
1541  *
1542  * The latter case arises if the tuple is modified by a
1543  * command in a BEFORE trigger, or perhaps by a command in a
1544  * volatile function used in the query. In such situations we
1545  * should not ignore the deletion, but it is equally unsafe to
1546  * proceed. We don't want to discard the original DELETE
1547  * while keeping the triggered actions based on its deletion;
1548  * and it would be no better to allow the original DELETE
1549  * while discarding updates that it triggered. The row update
1550  * carries some information that might be important according
1551  * to business rules; so throwing an error is the only safe
1552  * course.
1553  *
1554  * If a trigger actually intends this type of interaction, it
1555  * can re-execute the DELETE and then return NULL to cancel
1556  * the outer delete.
1557  */
1558  if (context->tmfd.cmax != estate->es_output_cid)
1559  ereport(ERROR,
1560  (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
1561  errmsg("tuple to be deleted was already modified by an operation triggered by the current command"),
1562  errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
1563 
1564  /* Else, already deleted by self; nothing to do */
1565  return NULL;
1566 
1567  case TM_Ok:
1568  break;
1569 
1570  case TM_Updated:
1571  {
1572  TupleTableSlot *inputslot;
1573  TupleTableSlot *epqslot;
1574 
1576  ereport(ERROR,
1578  errmsg("could not serialize access due to concurrent update")));
1579 
1580  /*
1581  * ExecDeleteAct() has already locked the old tuple for
1582  * us. Now we need to copy it to the right slot.
1583  */
1584  EvalPlanQualBegin(context->epqstate);
1585  inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
1586  resultRelInfo->ri_RangeTableIndex);
1587 
1588  /*
1589  * Save locked table for further processing for RETURNING
1590  * clause.
1591  */
1592  if (processReturning &&
1593  resultRelInfo->ri_projectReturning &&
1594  !resultRelInfo->ri_FdwRoutine)
1595  {
1596  TupleTableSlot *returningSlot;
1597 
1598  returningSlot = ExecGetReturningSlot(estate,
1599  resultRelInfo);
1600  ExecCopySlot(returningSlot, inputslot);
1601  ExecMaterializeSlot(returningSlot);
1602  }
1603 
1604  Assert(context->tmfd.traversed);
1605  epqslot = EvalPlanQual(context->epqstate,
1606  resultRelationDesc,
1607  resultRelInfo->ri_RangeTableIndex,
1608  inputslot);
1609  if (TupIsNull(epqslot))
1610  /* Tuple not passing quals anymore, exiting... */
1611  return NULL;
1612 
1613  /*
1614  * If requested, skip delete and pass back the updated
1615  * row.
1616  */
1617  if (epqreturnslot)
1618  {
1619  *epqreturnslot = epqslot;
1620  return NULL;
1621  }
1622  else
1623  goto ldelete;
1624  }
1625 
1626  case TM_Deleted:
1628  ereport(ERROR,
1630  errmsg("could not serialize access due to concurrent delete")));
1631  /* tuple already deleted; nothing to do */
1632  return NULL;
1633 
1634  default:
1635  elog(ERROR, "unrecognized table_tuple_delete status: %u",
1636  result);
1637  return NULL;
1638  }
1639 
1640  /*
1641  * Note: Normally one would think that we have to delete index tuples
1642  * associated with the heap tuple now...
1643  *
1644  * ... but in POSTGRES, we have no need to do this because VACUUM will
1645  * take care of it later. We can't delete index tuples immediately
1646  * anyway, since the tuple is still visible to other transactions.
1647  */
1648  }
1649 
1650  if (canSetTag)
1651  (estate->es_processed)++;
1652 
1653  /* Tell caller that the delete actually happened. */
1654  if (tupleDeleted)
1655  *tupleDeleted = true;
1656 
1657  ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);
1658 
1659  /* Process RETURNING if present and if requested */
1660  if (processReturning && resultRelInfo->ri_projectReturning)
1661  {
1662  /*
1663  * We have to put the target tuple into a slot, which means first we
1664  * gotta fetch it. We can use the trigger tuple slot.
1665  */
1666  TupleTableSlot *rslot;
1667 
1668  if (resultRelInfo->ri_FdwRoutine)
1669  {
1670  /* FDW must have provided a slot containing the deleted row */
1671  Assert(!TupIsNull(slot));
1672  }
1673  else
1674  {
1675  /*
1676  * Tuple can be already fetched to the returning slot in case
1677  * we've previously locked it. Fetch the tuple only if the slot
1678  * is empty.
1679  */
1680  slot = ExecGetReturningSlot(estate, resultRelInfo);
1681  if (oldtuple != NULL)
1682  {
1683  ExecForceStoreHeapTuple(oldtuple, slot, false);
1684  }
1685  else if (TupIsNull(slot))
1686  {
1687  if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
1688  SnapshotAny, slot))
1689  elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
1690  }
1691  }
1692 
1693  rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot);
1694 
1695  /*
1696  * Before releasing the target tuple again, make sure rslot has a
1697  * local copy of any pass-by-reference values.
1698  */
1699  ExecMaterializeSlot(rslot);
1700 
1701  ExecClearTuple(slot);
1702 
1703  return rslot;
1704  }
1705 
1706  return NULL;
1707 }
void EvalPlanQualBegin(EPQState *epqstate)
Definition: execMain.c:2720
TupleTableSlot * EvalPlanQual(EPQState *epqstate, Relation relation, Index rti, TupleTableSlot *inputslot)
Definition: execMain.c:2457
TupleTableSlot * EvalPlanQualSlot(EPQState *epqstate, Relation relation, Index rti)
Definition: execMain.c:2567
TupleTableSlot * ExecStoreAllNullTuple(TupleTableSlot *slot)
Definition: execTuples.c:1576
void ExecForceStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot, bool shouldFree)
Definition: execTuples.c:1469
TupleTableSlot * ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
Definition: execUtils.c:1213
static bool ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot **epqreturnslot, TM_Result *result)
static TM_Result ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool changingPart, bool lockUpdated)
static TupleTableSlot * ExecProcessReturning(ResultRelInfo *resultRelInfo, TupleTableSlot *tupleSlot, TupleTableSlot *planSlot)
static void ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, bool changingPart)
CommandId es_output_cid
Definition: execnodes.h:632
ExecForeignDelete_function ExecForeignDelete
Definition: fdwapi.h:236
TM_FailureData tmfd
TupleTableSlot * planSlot
Index ri_RangeTableIndex
Definition: execnodes.h:447
ProjectionInfo * ri_projectReturning
Definition: execnodes.h:533
bool traversed
Definition: tableam.h:145
CommandId cmax
Definition: tableam.h:144
bool trig_delete_instead_row
Definition: reltrigger.h:68
TM_Result
Definition: tableam.h:72
@ TM_Ok
Definition: tableam.h:77
@ TM_Deleted
Definition: tableam.h:92
@ TM_Updated
Definition: tableam.h:89
@ TM_SelfModified
Definition: tableam.h:83
bool ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple)
Definition: trigger.c:2822
#define TTS_EMPTY(slot)
Definition: tuptable.h:97
static TupleTableSlot * ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
Definition: tuptable.h:521

References Assert(), TM_FailureData::cmax, elog(), ModifyTableContext::epqstate, ereport, errcode(), ERRCODE_T_R_SERIALIZATION_FAILURE, errhint(), errmsg(), ERROR, EState::es_output_cid, EState::es_processed, ModifyTableContext::estate, EvalPlanQual(), EvalPlanQualBegin(), EvalPlanQualSlot(), ExecClearTuple(), ExecCopySlot(), ExecDeleteAct(), ExecDeleteEpilogue(), ExecDeletePrologue(), ExecForceStoreHeapTuple(), FdwRoutine::ExecForeignDelete, ExecGetReturningSlot(), ExecIRDeleteTriggers(), ExecMaterializeSlot(), ExecProcessReturning(), ExecStoreAllNullTuple(), IsolationUsesXactSnapshot, ModifyTableContext::planSlot, RelationGetRelid, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RangeTableIndex, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, SnapshotAny, table_tuple_fetch_row_version(), TM_Deleted, TM_Ok, TM_SelfModified, TM_Updated, ModifyTableContext::tmfd, TM_FailureData::traversed, TriggerDesc::trig_delete_instead_row, TTS_EMPTY, TupleTableSlot::tts_tableOid, and TupIsNull.

Referenced by ExecCrossPartitionUpdate(), and ExecModifyTable().

◆ ExecDeleteAct()

static TM_Result ExecDeleteAct ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
bool  changingPart,
bool  lockUpdated 
)
static

Definition at line 1358 of file nodeModifyTable.c.

1360 {
1361  EState *estate = context->estate;
1362  GetEPQSlotArg slotArg = {context->epqstate, resultRelInfo};
1363  LazyTupleTableSlot lazyEPQSlot,
1364  *lazyEPQSlotPtr;
1365 
1366  if (lockUpdated)
1367  {
1368  MAKE_LAZY_TTS(&lazyEPQSlot, GetEPQSlot, &slotArg);
1369  lazyEPQSlotPtr = &lazyEPQSlot;
1370  }
1371  else
1372  {
1373  lazyEPQSlotPtr = NULL;
1374  }
1375  return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
1376  estate->es_output_cid,
1377  estate->es_snapshot,
1378  estate->es_crosscheck_snapshot,
1379  true /* wait for commit */ ,
1380  &context->tmfd,
1381  changingPart,
1382  lazyEPQSlotPtr);
1383 }
static TupleTableSlot * GetEPQSlot(void *arg)
Snapshot es_crosscheck_snapshot
Definition: execnodes.h:617
static TM_Result table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool changingPart, LazyTupleTableSlot *lockedSlot)
Definition: tableam.h:1490
#define MAKE_LAZY_TTS(lazySlot, callback, arg)
Definition: tuptable.h:323

References ModifyTableContext::epqstate, EState::es_crosscheck_snapshot, EState::es_output_cid, EState::es_snapshot, ModifyTableContext::estate, GetEPQSlot(), MAKE_LAZY_TTS, ResultRelInfo::ri_RelationDesc, table_tuple_delete(), and ModifyTableContext::tmfd.

Referenced by ExecDelete(), and ExecMergeMatched().

◆ ExecDeleteEpilogue()

static void ExecDeleteEpilogue ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
bool  changingPart 
)
static

Definition at line 1393 of file nodeModifyTable.c.

1395 {
1396  ModifyTableState *mtstate = context->mtstate;
1397  EState *estate = context->estate;
1398  TransitionCaptureState *ar_delete_trig_tcs;
1399 
1400  /*
1401  * If this delete is the result of a partition key update that moved the
1402  * tuple to a new partition, put this row into the transition OLD TABLE,
1403  * if there is one. We need to do this separately for DELETE and INSERT
1404  * because they happen on different tables.
1405  */
1406  ar_delete_trig_tcs = mtstate->mt_transition_capture;
1407  if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture &&
1409  {
1410  ExecARUpdateTriggers(estate, resultRelInfo,
1411  NULL, NULL,
1412  tupleid, oldtuple,
1413  NULL, NULL, mtstate->mt_transition_capture,
1414  false);
1415 
1416  /*
1417  * We've already captured the OLD TABLE row, so make sure any AR
1418  * DELETE trigger fired below doesn't capture it again.
1419  */
1420  ar_delete_trig_tcs = NULL;
1421  }
1422 
1423  /* AFTER ROW DELETE Triggers */
1424  ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
1425  ar_delete_trig_tcs, changingPart);
1426 }
CmdType operation
Definition: execnodes.h:1263
void ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TransitionCaptureState *transition_capture, bool is_crosspart_update)
Definition: trigger.c:2785

References CMD_UPDATE, ModifyTableContext::estate, ExecARDeleteTriggers(), ExecARUpdateTriggers(), ModifyTableState::mt_transition_capture, ModifyTableContext::mtstate, ModifyTableState::operation, and TransitionCaptureState::tcs_update_old_table.

Referenced by ExecDelete(), and ExecMergeMatched().

◆ ExecDeletePrologue()

static bool ExecDeletePrologue ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot **  epqreturnslot,
TM_Result result 
)
static

Definition at line 1304 of file nodeModifyTable.c.

1307 {
1308  if (result)
1309  *result = TM_Ok;
1310 
1311  /* BEFORE ROW DELETE triggers */
1312  if (resultRelInfo->ri_TrigDesc &&
1313  resultRelInfo->ri_TrigDesc->trig_delete_before_row)
1314  {
1315  /* Flush any pending inserts, so rows are visible to the triggers */
1317  ExecPendingInserts(context->estate);
1318 
1319  return ExecBRDeleteTriggers(context->estate, context->epqstate,
1320  resultRelInfo, tupleid, oldtuple,
1321  epqreturnslot, result, &context->tmfd);
1322  }
1323 
1324  return true;
1325 }
static void ExecPendingInserts(EState *estate)
List * es_insert_pending_result_relations
Definition: execnodes.h:713
bool trig_delete_before_row
Definition: reltrigger.h:66
bool ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot **epqslot, TM_Result *tmresult, TM_FailureData *tmfd)
Definition: trigger.c:2694

References ModifyTableContext::epqstate, EState::es_insert_pending_result_relations, ModifyTableContext::estate, ExecBRDeleteTriggers(), ExecPendingInserts(), NIL, ResultRelInfo::ri_TrigDesc, TM_Ok, ModifyTableContext::tmfd, and TriggerDesc::trig_delete_before_row.

Referenced by ExecDelete(), and ExecMergeMatched().

◆ ExecEndModifyTable()

void ExecEndModifyTable ( ModifyTableState node)

Definition at line 4377 of file nodeModifyTable.c.

4378 {
4379  int i;
4380 
4381  /*
4382  * Allow any FDWs to shut down
4383  */
4384  for (i = 0; i < node->mt_nrels; i++)
4385  {
4386  int j;
4387  ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
4388 
4389  if (!resultRelInfo->ri_usesFdwDirectModify &&
4390  resultRelInfo->ri_FdwRoutine != NULL &&
4391  resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
4392  resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
4393  resultRelInfo);
4394 
4395  /*
4396  * Cleanup the initialized batch slots. This only matters for FDWs
4397  * with batching, but the other cases will have ri_NumSlotsInitialized
4398  * == 0.
4399  */
4400  for (j = 0; j < resultRelInfo->ri_NumSlotsInitialized; j++)
4401  {
4402  ExecDropSingleTupleTableSlot(resultRelInfo->ri_Slots[j]);
4403  ExecDropSingleTupleTableSlot(resultRelInfo->ri_PlanSlots[j]);
4404  }
4405  }
4406 
4407  /*
4408  * Close all the partitioned tables, leaf partitions, and their indices
4409  * and release the slot used for tuple routing, if set.
4410  */
4411  if (node->mt_partition_tuple_routing)
4412  {
4414 
4415  if (node->mt_root_tuple_slot)
4417  }
4418 
4419  /*
4420  * Free the exprcontext
4421  */
4422  ExecFreeExprContext(&node->ps);
4423 
4424  /*
4425  * clean out the tuple table
4426  */
4427  if (node->ps.ps_ResultTupleSlot)
4429 
4430  /*
4431  * Terminate EPQ execution if active
4432  */
4433  EvalPlanQualEnd(&node->mt_epqstate);
4434 
4435  /*
4436  * shut down subplan
4437  */
4438  ExecEndNode(outerPlanState(node));
4439 }
void EvalPlanQualEnd(EPQState *epqstate)
Definition: execMain.c:2933
void ExecCleanupTupleRouting(ModifyTableState *mtstate, PartitionTupleRouting *proute)
void ExecEndNode(PlanState *node)
Definition: execProcnode.c:557
void ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
Definition: execTuples.c:1254
void ExecFreeExprContext(PlanState *planstate)
Definition: execUtils.c:658
#define outerPlanState(node)
Definition: execnodes.h:1131
int j
Definition: isn.c:74
EndForeignModify_function EndForeignModify
Definition: fdwapi.h:237
ResultRelInfo * resultRelInfo
Definition: execnodes.h:1267
EPQState mt_epqstate
Definition: execnodes.h:1277
TupleTableSlot * ps_ResultTupleSlot
Definition: execnodes.h:1073
TupleTableSlot ** ri_Slots
Definition: execnodes.h:509
int ri_NumSlotsInitialized
Definition: execnodes.h:507
TupleTableSlot ** ri_PlanSlots
Definition: execnodes.h:510
bool ri_usesFdwDirectModify
Definition: execnodes.h:503

References FdwRoutine::EndForeignModify, EvalPlanQualEnd(), ExecCleanupTupleRouting(), ExecClearTuple(), ExecDropSingleTupleTableSlot(), ExecEndNode(), ExecFreeExprContext(), i, j, ModifyTableState::mt_epqstate, ModifyTableState::mt_nrels, ModifyTableState::mt_partition_tuple_routing, ModifyTableState::mt_root_tuple_slot, outerPlanState, ModifyTableState::ps, PlanState::ps_ResultTupleSlot, ModifyTableState::resultRelInfo, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_NumSlotsInitialized, ResultRelInfo::ri_PlanSlots, ResultRelInfo::ri_Slots, ResultRelInfo::ri_usesFdwDirectModify, and PlanState::state.

Referenced by ExecEndNode().

◆ ExecGetInsertNewTuple()

static TupleTableSlot* ExecGetInsertNewTuple ( ResultRelInfo relinfo,
TupleTableSlot planSlot 
)
static

Definition at line 670 of file nodeModifyTable.c.

672 {
673  ProjectionInfo *newProj = relinfo->ri_projectNew;
674  ExprContext *econtext;
675 
676  /*
677  * If there's no projection to be done, just make sure the slot is of the
678  * right type for the target rel. If the planSlot is the right type we
679  * can use it as-is, else copy the data into ri_newTupleSlot.
680  */
681  if (newProj == NULL)
682  {
683  if (relinfo->ri_newTupleSlot->tts_ops != planSlot->tts_ops)
684  {
685  ExecCopySlot(relinfo->ri_newTupleSlot, planSlot);
686  return relinfo->ri_newTupleSlot;
687  }
688  else
689  return planSlot;
690  }
691 
692  /*
693  * Else project; since the projection output slot is ri_newTupleSlot, this
694  * will also fix any slot-type problem.
695  *
696  * Note: currently, this is dead code, because INSERT cases don't receive
697  * any junk columns so there's never a projection to be done.
698  */
699  econtext = newProj->pi_exprContext;
700  econtext->ecxt_outertuple = planSlot;
701  return ExecProject(newProj);
702 }
static TupleTableSlot * ExecProject(ProjectionInfo *projInfo)
Definition: executor.h:374
TupleTableSlot * ecxt_outertuple
Definition: execnodes.h:253
ExprContext * pi_exprContext
Definition: execnodes.h:358
TupleTableSlot * ri_newTupleSlot
Definition: execnodes.h:473
ProjectionInfo * ri_projectNew
Definition: execnodes.h:471
const TupleTableSlotOps *const tts_ops
Definition: tuptable.h:122

References ExprContext::ecxt_outertuple, ExecCopySlot(), ExecProject(), ProjectionInfo::pi_exprContext, ResultRelInfo::ri_newTupleSlot, ResultRelInfo::ri_projectNew, and TupleTableSlot::tts_ops.

Referenced by ExecModifyTable().

◆ ExecGetUpdateNewTuple()

TupleTableSlot* ExecGetUpdateNewTuple ( ResultRelInfo relinfo,
TupleTableSlot planSlot,
TupleTableSlot oldSlot 
)

Definition at line 714 of file nodeModifyTable.c.

717 {
718  ProjectionInfo *newProj = relinfo->ri_projectNew;
719  ExprContext *econtext;
720 
721  /* Use a few extra Asserts to protect against outside callers */
722  Assert(relinfo->ri_projectNewInfoValid);
723  Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
724  Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
725 
726  econtext = newProj->pi_exprContext;
727  econtext->ecxt_outertuple = planSlot;
728  econtext->ecxt_scantuple = oldSlot;
729  return ExecProject(newProj);
730 }

References Assert(), ExprContext::ecxt_outertuple, ExprContext::ecxt_scantuple, ExecProject(), ProjectionInfo::pi_exprContext, ResultRelInfo::ri_projectNew, ResultRelInfo::ri_projectNewInfoValid, and TTS_EMPTY.

Referenced by ExecBRUpdateTriggers(), ExecCrossPartitionUpdate(), ExecModifyTable(), and ExecUpdate().

◆ ExecInitInsertProjection()

static void ExecInitInsertProjection ( ModifyTableState mtstate,
ResultRelInfo resultRelInfo 
)
static

Definition at line 542 of file nodeModifyTable.c.

544 {
545  ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
546  Plan *subplan = outerPlan(node);
547  EState *estate = mtstate->ps.state;
548  List *insertTargetList = NIL;
549  bool need_projection = false;
550  ListCell *l;
551 
552  /* Extract non-junk columns of the subplan's result tlist. */
553  foreach(l, subplan->targetlist)
554  {
555  TargetEntry *tle = (TargetEntry *) lfirst(l);
556 
557  if (!tle->resjunk)
558  insertTargetList = lappend(insertTargetList, tle);
559  else
560  need_projection = true;
561  }
562 
563  /*
564  * The junk-free list must produce a tuple suitable for the result
565  * relation.
566  */
567  ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, insertTargetList);
568 
569  /* We'll need a slot matching the table's format. */
570  resultRelInfo->ri_newTupleSlot =
571  table_slot_create(resultRelInfo->ri_RelationDesc,
572  &estate->es_tupleTable);
573 
574  /* Build ProjectionInfo if needed (it probably isn't). */
575  if (need_projection)
576  {
577  TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
578 
579  /* need an expression context to do the projection */
580  if (mtstate->ps.ps_ExprContext == NULL)
581  ExecAssignExprContext(estate, &mtstate->ps);
582 
583  resultRelInfo->ri_projectNew =
584  ExecBuildProjectionInfo(insertTargetList,
585  mtstate->ps.ps_ExprContext,
586  resultRelInfo->ri_newTupleSlot,
587  &mtstate->ps,
588  relDesc);
589  }
590 
591  resultRelInfo->ri_projectNewInfoValid = true;
592 }
ProjectionInfo * ExecBuildProjectionInfo(List *targetList, ExprContext *econtext, TupleTableSlot *slot, PlanState *parent, TupleDesc inputDesc)
Definition: execExpr.c:357
void ExecAssignExprContext(EState *estate, PlanState *planstate)
Definition: execUtils.c:488
List * lappend(List *list, void *datum)
Definition: list.c:338
static void ExecCheckPlanOutput(Relation resultRel, List *targetList)
#define outerPlan(node)
Definition: plannodes.h:186
List * es_tupleTable
Definition: execnodes.h:662
ExprContext * ps_ExprContext
Definition: execnodes.h:1074

References EState::es_tupleTable, ExecAssignExprContext(), ExecBuildProjectionInfo(), ExecCheckPlanOutput(), lappend(), lfirst, NIL, outerPlan, PlanState::plan, ModifyTableState::ps, PlanState::ps_ExprContext, RelationGetDescr, ResultRelInfo::ri_newTupleSlot, ResultRelInfo::ri_projectNew, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_RelationDesc, PlanState::state, and table_slot_create().

Referenced by ExecModifyTable().

◆ ExecInitMerge()

void ExecInitMerge ( ModifyTableState mtstate,
EState estate 
)
static

Definition at line 3165 of file nodeModifyTable.c.

3166 {
3167  ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
3168  ResultRelInfo *rootRelInfo = mtstate->rootResultRelInfo;
3169  ResultRelInfo *resultRelInfo;
3170  ExprContext *econtext;
3171  ListCell *lc;
3172  int i;
3173 
3174  if (node->mergeActionLists == NIL)
3175  return;
3176 
3177  mtstate->mt_merge_subcommands = 0;
3178 
3179  if (mtstate->ps.ps_ExprContext == NULL)
3180  ExecAssignExprContext(estate, &mtstate->ps);
3181  econtext = mtstate->ps.ps_ExprContext;
3182 
3183  /*
3184  * Create a MergeActionState for each action on the mergeActionList and
3185  * add it to either a list of matched actions or not-matched actions.
3186  *
3187  * Similar logic appears in ExecInitPartitionInfo(), so if changing
3188  * anything here, do so there too.
3189  */
3190  i = 0;
3191  foreach(lc, node->mergeActionLists)
3192  {
3193  List *mergeActionList = lfirst(lc);
3194  TupleDesc relationDesc;
3195  ListCell *l;
3196 
3197  resultRelInfo = mtstate->resultRelInfo + i;
3198  i++;
3199  relationDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
3200 
3201  /* initialize slots for MERGE fetches from this rel */
3202  if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
3203  ExecInitMergeTupleSlots(mtstate, resultRelInfo);
3204 
3205  foreach(l, mergeActionList)
3206  {
3208  MergeActionState *action_state;
3209  TupleTableSlot *tgtslot;
3210  TupleDesc tgtdesc;
3211  List **list;
3212 
3213  /*
3214  * Build action merge state for this rel. (For partitions,
3215  * equivalent code exists in ExecInitPartitionInfo.)
3216  */
3217  action_state = makeNode(MergeActionState);
3218  action_state->mas_action = action;
3219  action_state->mas_whenqual = ExecInitQual((List *) action->qual,
3220  &mtstate->ps);
3221 
3222  /*
3223  * We create two lists - one for WHEN MATCHED actions and one for
3224  * WHEN NOT MATCHED actions - and stick the MergeActionState into
3225  * the appropriate list.
3226  */
3227  if (action_state->mas_action->matched)
3228  list = &resultRelInfo->ri_matchedMergeAction;
3229  else
3230  list = &resultRelInfo->ri_notMatchedMergeAction;
3231  *list = lappend(*list, action_state);
3232 
3233  switch (action->commandType)
3234  {
3235  case CMD_INSERT:
3236  ExecCheckPlanOutput(rootRelInfo->ri_RelationDesc,
3237  action->targetList);
3238 
3239  /*
3240  * If the MERGE targets a partitioned table, any INSERT
3241  * actions must be routed through it, not the child
3242  * relations. Initialize the routing struct and the root
3243  * table's "new" tuple slot for that, if not already done.
3244  * The projection we prepare, for all relations, uses the
3245  * root relation descriptor, and targets the plan's root
3246  * slot. (This is consistent with the fact that we
3247  * checked the plan output to match the root relation,
3248  * above.)
3249  */
3250  if (rootRelInfo->ri_RelationDesc->rd_rel->relkind ==
3251  RELKIND_PARTITIONED_TABLE)
3252  {
3253  if (mtstate->mt_partition_tuple_routing == NULL)
3254  {
3255  /*
3256  * Initialize planstate for routing if not already
3257  * done.
3258  *
3259  * Note that the slot is managed as a standalone
3260  * slot belonging to ModifyTableState, so we pass
3261  * NULL for the 2nd argument.
3262  */
3263  mtstate->mt_root_tuple_slot =
3264  table_slot_create(rootRelInfo->ri_RelationDesc,
3265  NULL);
3266  mtstate->mt_partition_tuple_routing =
3268  rootRelInfo->ri_RelationDesc);
3269  }
3270  tgtslot = mtstate->mt_root_tuple_slot;
3271  tgtdesc = RelationGetDescr(rootRelInfo->ri_RelationDesc);
3272  }
3273  else
3274  {
3275  /* not partitioned? use the stock relation and slot */
3276  tgtslot = resultRelInfo->ri_newTupleSlot;
3277  tgtdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
3278  }
3279 
3280  action_state->mas_proj =
3281  ExecBuildProjectionInfo(action->targetList, econtext,
3282  tgtslot,
3283  &mtstate->ps,
3284  tgtdesc);
3285 
3286  mtstate->mt_merge_subcommands |= MERGE_INSERT;
3287  break;
3288  case CMD_UPDATE:
3289  action_state->mas_proj =
3290  ExecBuildUpdateProjection(action->targetList,
3291  true,
3292  action->updateColnos,
3293  relationDesc,
3294  econtext,
3295  resultRelInfo->ri_newTupleSlot,
3296  &mtstate->ps);
3297  mtstate->mt_merge_subcommands |= MERGE_UPDATE;
3298  break;
3299  case CMD_DELETE:
3300  mtstate->mt_merge_subcommands |= MERGE_DELETE;
3301  break;
3302  case CMD_NOTHING:
3303  break;
3304  default:
3305  elog(ERROR, "unknown operation");
3306  break;
3307  }
3308  }
3309  }
3310 }
ProjectionInfo * ExecBuildUpdateProjection(List *targetList, bool evalTargetList, List *targetColnos, TupleDesc relDesc, ExprContext *econtext, TupleTableSlot *slot, PlanState *parent)
Definition: execExpr.c:517
ExprState * ExecInitQual(List *qual, PlanState *parent)
Definition: execExpr.c:213
#define MERGE_UPDATE
Definition: execnodes.h:1253
#define MERGE_INSERT
Definition: execnodes.h:1252
#define MERGE_DELETE
Definition: execnodes.h:1254
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:77
void ExecInitMergeTupleSlots(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
@ CMD_INSERT
Definition: nodes.h:278
@ CMD_DELETE
Definition: nodes.h:279
@ CMD_NOTHING
Definition: nodes.h:283
#define makeNode(_type_)
Definition: nodes.h:176
MergeAction * mas_action
Definition: execnodes.h:419
ProjectionInfo * mas_proj
Definition: execnodes.h:420
ExprState * mas_whenqual
Definition: execnodes.h:422
int mt_merge_subcommands
Definition: execnodes.h:1307
List * mergeActionLists
Definition: plannodes.h:255

References generate_unaccent_rules::action, CMD_DELETE, CMD_INSERT, CMD_NOTHING, CMD_UPDATE, elog(), ERROR, ExecAssignExprContext(), ExecBuildProjectionInfo(), ExecBuildUpdateProjection(), ExecCheckPlanOutput(), ExecInitMergeTupleSlots(), ExecInitQual(), ExecSetupPartitionTupleRouting(), i, if(), lappend(), lfirst, sort-test::list, makeNode, MergeActionState::mas_action, MergeActionState::mas_proj, MergeActionState::mas_whenqual, MergeAction::matched, MERGE_DELETE, MERGE_INSERT, MERGE_UPDATE, ModifyTable::mergeActionLists, ModifyTableState::mt_merge_subcommands, ModifyTableState::mt_partition_tuple_routing, ModifyTableState::mt_root_tuple_slot, NIL, PlanState::plan, ModifyTableState::ps, PlanState::ps_ExprContext, RelationGetDescr, ModifyTableState::resultRelInfo, ModifyTableState::rootResultRelInfo, table_slot_create(), and unlikely.

Referenced by ExecInitModifyTable().

◆ ExecInitMergeTupleSlots()

void ExecInitMergeTupleSlots ( ModifyTableState mtstate,
ResultRelInfo resultRelInfo 
)

Definition at line 3319 of file nodeModifyTable.c.

3321 {
3322  EState *estate = mtstate->ps.state;
3323 
3324  Assert(!resultRelInfo->ri_projectNewInfoValid);
3325 
3326  resultRelInfo->ri_oldTupleSlot =
3327  table_slot_create(resultRelInfo->ri_RelationDesc,
3328  &estate->es_tupleTable);
3329  resultRelInfo->ri_newTupleSlot =
3330  table_slot_create(resultRelInfo->ri_RelationDesc,
3331  &estate->es_tupleTable);
3332  resultRelInfo->ri_projectNewInfoValid = true;
3333 }

References Assert(), EState::es_tupleTable, ModifyTableState::ps, ResultRelInfo::ri_newTupleSlot, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_RelationDesc, PlanState::state, and table_slot_create().

Referenced by ExecInitMerge(), and ExecInitPartitionInfo().

◆ ExecInitModifyTable()

ModifyTableState* ExecInitModifyTable ( ModifyTable node,
EState estate,
int  eflags 
)

Definition at line 3897 of file nodeModifyTable.c.

3898 {
3899  ModifyTableState *mtstate;
3900  Plan *subplan = outerPlan(node);
3901  CmdType operation = node->operation;
3902  int nrels = list_length(node->resultRelations);
3903  ResultRelInfo *resultRelInfo;
3904  List *arowmarks;
3905  ListCell *l;
3906  int i;
3907  Relation rel;
3908 
3909  /* check for unsupported flags */
3910  Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
3911 
3912  /*
3913  * create state structure
3914  */
3915  mtstate = makeNode(ModifyTableState);
3916  mtstate->ps.plan = (Plan *) node;
3917  mtstate->ps.state = estate;
3918  mtstate->ps.ExecProcNode = ExecModifyTable;
3919 
3920  mtstate->operation = operation;
3921  mtstate->canSetTag = node->canSetTag;
3922  mtstate->mt_done = false;
3923 
3924  mtstate->mt_nrels = nrels;
3925  mtstate->resultRelInfo = (ResultRelInfo *)
3926  palloc(nrels * sizeof(ResultRelInfo));
3927 
3928  mtstate->mt_merge_inserted = 0;
3929  mtstate->mt_merge_updated = 0;
3930  mtstate->mt_merge_deleted = 0;
3931 
3932  /*----------
3933  * Resolve the target relation. This is the same as:
3934  *
3935  * - the relation for which we will fire FOR STATEMENT triggers,
3936  * - the relation into whose tuple format all captured transition tuples
3937  * must be converted, and
3938  * - the root partitioned table used for tuple routing.
3939  *
3940  * If it's a partitioned table, the root partition doesn't appear
3941  * elsewhere in the plan and its RT index is given explicitly in
3942  * node->rootRelation. Otherwise (i.e. table inheritance) the target
3943  * relation is the first relation in the node->resultRelations list.
3944  *----------
3945  */
3946  if (node->rootRelation > 0)
3947  {
3949  ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
3950  node->rootRelation);
3951  }
3952  else
3953  {
3954  mtstate->rootResultRelInfo = mtstate->resultRelInfo;
3955  ExecInitResultRelation(estate, mtstate->resultRelInfo,
3956  linitial_int(node->resultRelations));
3957  }
3958 
3959  /* set up epqstate with dummy subplan data for the moment */
3960  EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
3961  mtstate->fireBSTriggers = true;
3962 
3963  /*
3964  * Build state for collecting transition tuples. This requires having a
3965  * valid trigger query context, so skip it in explain-only mode.
3966  */
3967  if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
3968  ExecSetupTransitionCaptureState(mtstate, estate);
3969 
3970  /*
3971  * Open all the result relations and initialize the ResultRelInfo structs.
3972  * (But root relation was initialized above, if it's part of the array.)
3973  * We must do this before initializing the subplan, because direct-modify
3974  * FDWs expect their ResultRelInfos to be available.
3975  */
3976  resultRelInfo = mtstate->resultRelInfo;
3977  i = 0;
3978  foreach(l, node->resultRelations)
3979  {
3980  Index resultRelation = lfirst_int(l);
3981 
3982  if (resultRelInfo != mtstate->rootResultRelInfo)
3983  {
3984  ExecInitResultRelation(estate, resultRelInfo, resultRelation);
3985 
3986  /*
3987  * For child result relations, store the root result relation
3988  * pointer. We do so for the convenience of places that want to
3989  * look at the query's original target relation but don't have the
3990  * mtstate handy.
3991  */
3992  resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
3993  }
3994 
3995  /* Initialize the usesFdwDirectModify flag */
3996  resultRelInfo->ri_usesFdwDirectModify =
3998 
3999  /*
4000  * Verify result relation is a valid target for the current operation
4001  */
4002  CheckValidResultRel(resultRelInfo, operation);
4003 
4004  resultRelInfo++;
4005  i++;
4006  }
4007 
4008  /*
4009  * Now we may initialize the subplan.
4010  */
4011  outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
4012 
4013  /*
4014  * Do additional per-result-relation initialization.
4015  */
4016  for (i = 0; i < nrels; i++)
4017  {
4018  resultRelInfo = &mtstate->resultRelInfo[i];
4019 
4020  /* Let FDWs init themselves for foreign-table result rels */
4021  if (!resultRelInfo->ri_usesFdwDirectModify &&
4022  resultRelInfo->ri_FdwRoutine != NULL &&
4023  resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
4024  {
4025  List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
4026 
4027  resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
4028  resultRelInfo,
4029  fdw_private,
4030  i,
4031  eflags);
4032  }
4033 
4034  /*
4035  * For UPDATE/DELETE/MERGE, find the appropriate junk attr now, either
4036  * a 'ctid' or 'wholerow' attribute depending on relkind. For foreign
4037  * tables, the FDW might have created additional junk attr(s), but
4038  * those are no concern of ours.
4039  */
4040  if (operation == CMD_UPDATE || operation == CMD_DELETE ||
4041  operation == CMD_MERGE)
4042  {
4043  char relkind;
4044 
4045  relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
4046  if (relkind == RELKIND_RELATION ||
4047  relkind == RELKIND_MATVIEW ||
4048  relkind == RELKIND_PARTITIONED_TABLE)
4049  {
4050  resultRelInfo->ri_RowIdAttNo =
4051  ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
4052  if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
4053  elog(ERROR, "could not find junk ctid column");
4054  }
4055  else if (relkind == RELKIND_FOREIGN_TABLE)
4056  {
4057  /*
4058  * We don't support MERGE with foreign tables for now. (It's
4059  * problematic because the implementation uses CTID.)
4060  */
4061  Assert(operation != CMD_MERGE);
4062 
4063  /*
4064  * When there is a row-level trigger, there should be a
4065  * wholerow attribute. We also require it to be present in
4066  * UPDATE and MERGE, so we can get the values of unchanged
4067  * columns.
4068  */
4069  resultRelInfo->ri_RowIdAttNo =
4071  "wholerow");
4072  if ((mtstate->operation == CMD_UPDATE || mtstate->operation == CMD_MERGE) &&
4073  !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
4074  elog(ERROR, "could not find junk wholerow column");
4075  }
4076  else
4077  {
4078  /* No support for MERGE */
4079  Assert(operation != CMD_MERGE);
4080  /* Other valid target relkinds must provide wholerow */
4081  resultRelInfo->ri_RowIdAttNo =
4083  "wholerow");
4084  if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
4085  elog(ERROR, "could not find junk wholerow column");
4086  }
4087  }
4088  }
4089 
4090  /*
4091  * If this is an inherited update/delete/merge, there will be a junk
4092  * attribute named "tableoid" present in the subplan's targetlist. It
4093  * will be used to identify the result relation for a given tuple to be
4094  * updated/deleted/merged.
4095  */
4096  mtstate->mt_resultOidAttno =
4097  ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
4098  Assert(AttributeNumberIsValid(mtstate->mt_resultOidAttno) || nrels == 1);
4099  mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */
4100  mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */
4101 
4102  /* Get the root target relation */
4103  rel = mtstate->rootResultRelInfo->ri_RelationDesc;
4104 
4105  /*
4106  * Build state for tuple routing if it's a partitioned INSERT. An UPDATE
4107  * or MERGE might need this too, but only if it actually moves tuples
4108  * between partitions; in that case setup is done by
4109  * ExecCrossPartitionUpdate.
4110  */
4111  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
4112  operation == CMD_INSERT)
4113  mtstate->mt_partition_tuple_routing =
4114  ExecSetupPartitionTupleRouting(estate, rel);
4115 
4116  /*
4117  * Initialize any WITH CHECK OPTION constraints if needed.
4118  */
4119  resultRelInfo = mtstate->resultRelInfo;
4120  foreach(l, node->withCheckOptionLists)
4121  {
4122  List *wcoList = (List *) lfirst(l);
4123  List *wcoExprs = NIL;
4124  ListCell *ll;
4125 
4126  foreach(ll, wcoList)
4127  {
4128  WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
4129  ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
4130  &mtstate->ps);
4131 
4132  wcoExprs = lappend(wcoExprs, wcoExpr);
4133  }
4134 
4135  resultRelInfo->ri_WithCheckOptions = wcoList;
4136  resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
4137  resultRelInfo++;
4138  }
4139 
4140  /*
4141  * Initialize RETURNING projections if needed.
4142  */
4143  if (node->returningLists)
4144  {
4145  TupleTableSlot *slot;
4146  ExprContext *econtext;
4147 
4148  /*
4149  * Initialize result tuple slot and assign its rowtype using the first
4150  * RETURNING list. We assume the rest will look the same.
4151  */
4152  mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
4153 
4154  /* Set up a slot for the output of the RETURNING projection(s) */
4156  slot = mtstate->ps.ps_ResultTupleSlot;
4157 
4158  /* Need an econtext too */
4159  if (mtstate->ps.ps_ExprContext == NULL)
4160  ExecAssignExprContext(estate, &mtstate->ps);
4161  econtext = mtstate->ps.ps_ExprContext;
4162 
4163  /*
4164  * Build a projection for each result rel.
4165  */
4166  resultRelInfo = mtstate->resultRelInfo;
4167  foreach(l, node->returningLists)
4168  {
4169  List *rlist = (List *) lfirst(l);
4170 
4171  resultRelInfo->ri_returningList = rlist;
4172  resultRelInfo->ri_projectReturning =
4173  ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
4174  resultRelInfo->ri_RelationDesc->rd_att);
4175  resultRelInfo++;
4176  }
4177  }
4178  else
4179  {
4180  /*
4181  * We still must construct a dummy result tuple type, because InitPlan
4182  * expects one (maybe should change that?).
4183  */
4184  mtstate->ps.plan->targetlist = NIL;
4185  ExecInitResultTypeTL(&mtstate->ps);
4186 
4187  mtstate->ps.ps_ExprContext = NULL;
4188  }
4189 
4190  /* Set the list of arbiter indexes if needed for ON CONFLICT */
4191  resultRelInfo = mtstate->resultRelInfo;
4192  if (node->onConflictAction != ONCONFLICT_NONE)
4193  {
4194  /* insert may only have one relation, inheritance is not expanded */
4195  Assert(nrels == 1);
4196  resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
4197  }
4198 
4199  /*
4200  * If needed, Initialize target list, projection and qual for ON CONFLICT
4201  * DO UPDATE.
4202  */
4203  if (node->onConflictAction == ONCONFLICT_UPDATE)
4204  {
4206  ExprContext *econtext;
4207  TupleDesc relationDesc;
4208 
4209  /* already exists if created by RETURNING processing above */
4210  if (mtstate->ps.ps_ExprContext == NULL)
4211  ExecAssignExprContext(estate, &mtstate->ps);
4212 
4213  econtext = mtstate->ps.ps_ExprContext;
4214  relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
4215 
4216  /* create state for DO UPDATE SET operation */
4217  resultRelInfo->ri_onConflict = onconfl;
4218 
4219  /* initialize slot for the existing tuple */
4220  onconfl->oc_Existing =
4221  table_slot_create(resultRelInfo->ri_RelationDesc,
4222  &mtstate->ps.state->es_tupleTable);
4223 
4224  /*
4225  * Create the tuple slot for the UPDATE SET projection. We want a slot
4226  * of the table's type here, because the slot will be used to insert
4227  * into the table, and for RETURNING processing - which may access
4228  * system attributes.
4229  */
4230  onconfl->oc_ProjSlot =
4231  table_slot_create(resultRelInfo->ri_RelationDesc,
4232  &mtstate->ps.state->es_tupleTable);
4233 
4234  /* build UPDATE SET projection state */
4235  onconfl->oc_ProjInfo =
4237  true,
4238  node->onConflictCols,
4239  relationDesc,
4240  econtext,
4241  onconfl->oc_ProjSlot,
4242  &mtstate->ps);
4243 
4244  /* initialize state to evaluate the WHERE clause, if any */
4245  if (node->onConflictWhere)
4246  {
4247  ExprState *qualexpr;
4248 
4249  qualexpr = ExecInitQual((List *) node->onConflictWhere,
4250  &mtstate->ps);
4251  onconfl->oc_WhereClause = qualexpr;
4252  }
4253  }
4254 
4255  /*
4256  * If we have any secondary relations in an UPDATE or DELETE, they need to
4257  * be treated like non-locked relations in SELECT FOR UPDATE, ie, the
4258  * EvalPlanQual mechanism needs to be told about them. Locate the
4259  * relevant ExecRowMarks.
4260  */
4261  arowmarks = NIL;
4262  foreach(l, node->rowMarks)
4263  {
4265  ExecRowMark *erm;
4266  ExecAuxRowMark *aerm;
4267 
4268  /* ignore "parent" rowmarks; they are irrelevant at runtime */
4269  if (rc->isParent)
4270  continue;
4271 
4272  /* Find ExecRowMark and build ExecAuxRowMark */
4273  erm = ExecFindRowMark(estate, rc->rti, false);
4274  aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
4275  arowmarks = lappend(arowmarks, aerm);
4276  }
4277 
4278  /* For a MERGE command, initialize its state */
4279  if (mtstate->operation == CMD_MERGE)
4280  ExecInitMerge(mtstate, estate);
4281 
4282  EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
4283 
4284  /*
4285  * If there are a lot of result relations, use a hash table to speed the
4286  * lookups. If there are not a lot, a simple linear search is faster.
4287  *
4288  * It's not clear where the threshold is, but try 64 for starters. In a
4289  * debugging build, use a small threshold so that we get some test
4290  * coverage of both code paths.
4291  */
4292 #ifdef USE_ASSERT_CHECKING
4293 #define MT_NRELS_HASH 4
4294 #else
4295 #define MT_NRELS_HASH 64
4296 #endif
4297  if (nrels >= MT_NRELS_HASH)
4298  {
4299  HASHCTL hash_ctl;
4300 
4301  hash_ctl.keysize = sizeof(Oid);
4302  hash_ctl.entrysize = sizeof(MTTargetRelLookup);
4303  hash_ctl.hcxt = CurrentMemoryContext;
4304  mtstate->mt_resultOidHash =
4305  hash_create("ModifyTable target hash",
4306  nrels, &hash_ctl,
4308  for (i = 0; i < nrels; i++)
4309  {
4310  Oid hashkey;
4311  MTTargetRelLookup *mtlookup;
4312  bool found;
4313 
4314  resultRelInfo = &mtstate->resultRelInfo[i];
4315  hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc);
4316  mtlookup = (MTTargetRelLookup *)
4317  hash_search(mtstate->mt_resultOidHash, &hashkey,
4318  HASH_ENTER, &found);
4319  Assert(!found);
4320  mtlookup->relationIndex = i;
4321  }
4322  }
4323  else
4324  mtstate->mt_resultOidHash = NULL;
4325 
4326  /*
4327  * Determine if the FDW supports batch insert and determine the batch size
4328  * (a FDW may support batching, but it may be disabled for the
4329  * server/table).
4330  *
4331  * We only do this for INSERT, so that for UPDATE/DELETE the batch size
4332  * remains set to 0.
4333  */
4334  if (operation == CMD_INSERT)
4335  {
4336  /* insert may only have one relation, inheritance is not expanded */
4337  Assert(nrels == 1);
4338  resultRelInfo = mtstate->resultRelInfo;
4339  if (!resultRelInfo->ri_usesFdwDirectModify &&
4340  resultRelInfo->ri_FdwRoutine != NULL &&
4341  resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
4342  resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
4343  {
4344  resultRelInfo->ri_BatchSize =
4345  resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo);
4346  Assert(resultRelInfo->ri_BatchSize >= 1);
4347  }
4348  else
4349  resultRelInfo->ri_BatchSize = 1;
4350  }
4351 
4352  /*
4353  * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
4354  * to estate->es_auxmodifytables so that it will be run to completion by
4355  * ExecPostprocessPlan. (It'd actually work fine to add the primary
4356  * ModifyTable node too, but there's no need.) Note the use of lcons not
4357  * lappend: we need later-initialized ModifyTable nodes to be shut down
4358  * before earlier ones. This ensures that we don't throw away RETURNING
4359  * rows that need to be seen by a later CTE subplan.
4360  */
4361  if (!mtstate->canSetTag)
4362  estate->es_auxmodifytables = lcons(mtstate,
4363  estate->es_auxmodifytables);
4364 
4365  return mtstate;
4366 }
#define AttributeNumberIsValid(attributeNumber)
Definition: attnum.h:34
bool bms_is_member(int x, const Bitmapset *a)
Definition: bitmapset.c:444
unsigned int Index
Definition: c.h:598
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:953
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:350
AttrNumber ExecFindJunkAttributeInTlist(List *targetlist, const char *attrName)
Definition: execJunk.c:222
ExecAuxRowMark * ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
Definition: execMain.c:2387
void EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
Definition: execMain.c:2550
void EvalPlanQualInit(EPQState *epqstate, EState *parentestate, Plan *subplan, List *auxrowmarks, int epqParam)
Definition: execMain.c:2511
ExecRowMark * ExecFindRowMark(EState *estate, Index rti, bool missing_ok)
Definition: execMain.c:2364
void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
Definition: execMain.c:987
PlanState * ExecInitNode(Plan *node, EState *estate, int eflags)
Definition: execProcnode.c:142
const TupleTableSlotOps TTSOpsVirtual
Definition: execTuples.c:83
void ExecInitResultTypeTL(PlanState *planstate)
Definition: execTuples.c:1755
void ExecInitResultTupleSlotTL(PlanState *planstate, const TupleTableSlotOps *tts_ops)
Definition: execTuples.c:1799
void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, Index rti)
Definition: execUtils.c:845
#define EXEC_FLAG_BACKWARD
Definition: executor.h:68
#define EXEC_FLAG_EXPLAIN_ONLY
Definition: executor.h:65
#define EXEC_FLAG_MARK
Definition: executor.h:69
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_CONTEXT
Definition: hsearch.h:102
#define HASH_ELEM
Definition: hsearch.h:95
#define HASH_BLOBS
Definition: hsearch.h:97
List * lcons(void *datum, List *list)
Definition: list.c:494
MemoryContext CurrentMemoryContext
Definition: mcxt.c:135
static void ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
static TupleTableSlot * ExecModifyTable(PlanState *pstate)
struct MTTargetRelLookup MTTargetRelLookup
#define MT_NRELS_HASH
static void ExecInitMerge(ModifyTableState *mtstate, EState *estate)
@ ONCONFLICT_NONE
Definition: nodes.h:426
CmdType
Definition: nodes.h:274
@ CMD_MERGE
Definition: nodes.h:280
#define lfirst_node(type, lc)
Definition: pg_list.h:176
static int list_length(const List *l)
Definition: pg_list.h:152
#define lfirst_int(lc)
Definition: pg_list.h:173
#define linitial_int(l)
Definition: pg_list.h:179
#define linitial(l)
Definition: pg_list.h:178
static void * list_nth(const List *list, int n)
Definition: pg_list.h:299
#define InvalidOid
Definition: postgres_ext.h:36
unsigned int Oid
Definition: postgres_ext.h:31
List * es_auxmodifytables
Definition: execnodes.h:674
BeginForeignModify_function BeginForeignModify
Definition: fdwapi.h:231
GetForeignModifyBatchSize_function GetForeignModifyBatchSize
Definition: fdwapi.h:234
Size keysize
Definition: hsearch.h:75
Size entrysize
Definition: hsearch.h:76
MemoryContext hcxt
Definition: hsearch.h:86
double mt_merge_deleted
Definition: execnodes.h:1312
double mt_merge_inserted
Definition: execnodes.h:1310
double mt_merge_updated
Definition: execnodes.h:1311
HTAB * mt_resultOidHash
Definition: execnodes.h:1289
List * arbiterIndexes
Definition: plannodes.h:249
List * onConflictCols
Definition: plannodes.h:251
CmdType operation
Definition: plannodes.h:235
int epqParam
Definition: plannodes.h:247
List * resultRelations
Definition: plannodes.h:240
Bitmapset * fdwDirectModifyPlans
Definition: plannodes.h:245
List * onConflictSet
Definition: plannodes.h:250
bool canSetTag
Definition: plannodes.h:236
List * fdwPrivLists
Definition: plannodes.h:244
List * returningLists
Definition: plannodes.h:243
List * withCheckOptionLists
Definition: plannodes.h:242
Index rootRelation
Definition: plannodes.h:238
Node * onConflictWhere
Definition: plannodes.h:252
List * rowMarks
Definition: plannodes.h:246
OnConflictAction onConflictAction
Definition: plannodes.h:248
TupleTableSlot * oc_ProjSlot
Definition: execnodes.h:404
TupleTableSlot * oc_Existing
Definition: execnodes.h:403
ExprState * oc_WhereClause
Definition: execnodes.h:406
ProjectionInfo * oc_ProjInfo
Definition: execnodes.h:405
bool isParent
Definition: plannodes.h:1390
ExecProcNodeMtd ExecProcNode
Definition: execnodes.h:1041
List * targetlist
Definition: plannodes.h:156
TupleDesc rd_att
Definition: rel.h:111
Form_pg_class rd_rel
Definition: rel.h:110
OnConflictSetState * ri_onConflict
Definition: execnodes.h:539
List * ri_onConflictArbiterIndexes
Definition: execnodes.h:536
List * ri_WithCheckOptionExprs
Definition: execnodes.h:516
List * ri_returningList
Definition: execnodes.h:530
AttrNumber ri_RowIdAttNo
Definition: execnodes.h:465
int ri_BatchSize
Definition: execnodes.h:508

References ModifyTable::arbiterIndexes, Assert(), AttributeNumberIsValid, FdwRoutine::BeginForeignModify, bms_is_member(), ModifyTableState::canSetTag, ModifyTable::canSetTag, CheckValidResultRel(), CMD_DELETE, CMD_INSERT, CMD_MERGE, CMD_UPDATE, CurrentMemoryContext, elog(), HASHCTL::entrysize, ModifyTable::epqParam, ERROR, EState::es_auxmodifytables, EState::es_tupleTable, EvalPlanQualInit(), EvalPlanQualSetPlan(), EXEC_FLAG_BACKWARD, EXEC_FLAG_EXPLAIN_ONLY, EXEC_FLAG_MARK, ExecAssignExprContext(), ExecBuildAuxRowMark(), ExecBuildProjectionInfo(), ExecBuildUpdateProjection(), ExecFindJunkAttributeInTlist(), ExecFindRowMark(), FdwRoutine::ExecForeignBatchInsert, ExecInitMerge(), ExecInitNode(), ExecInitQual(), ExecInitResultRelation(), ExecInitResultTupleSlotTL(), ExecInitResultTypeTL(), ExecModifyTable(), PlanState::ExecProcNode, ExecSetupPartitionTupleRouting(), ExecSetupTransitionCaptureState(), ModifyTable::fdwDirectModifyPlans, ModifyTable::fdwPrivLists, ModifyTableState::fireBSTriggers, FdwRoutine::GetForeignModifyBatchSize, HASH_BLOBS, HASH_CONTEXT, hash_create(), HASH_ELEM, HASH_ENTER, hash_search(), HASHCTL::hcxt, i, InvalidOid, PlanRowMark::isParent, HASHCTL::keysize, lappend(), lcons(), lfirst, lfirst_int, lfirst_node, linitial, linitial_int, list_length(), list_nth(), makeNode, ModifyTableState::mt_done, ModifyTableState::mt_epqstate, ModifyTableState::mt_lastResultIndex, ModifyTableState::mt_lastResultOid, ModifyTableState::mt_merge_deleted, ModifyTableState::mt_merge_inserted, ModifyTableState::mt_merge_updated, ModifyTableState::mt_nrels, MT_NRELS_HASH, ModifyTableState::mt_partition_tuple_routing, ModifyTableState::mt_resultOidAttno, ModifyTableState::mt_resultOidHash, NIL, OnConflictSetState::oc_Existing, OnConflictSetState::oc_ProjInfo, OnConflictSetState::oc_ProjSlot, OnConflictSetState::oc_WhereClause, ONCONFLICT_NONE, ONCONFLICT_UPDATE, ModifyTable::onConflictAction, ModifyTable::onConflictCols, ModifyTable::onConflictSet, ModifyTable::onConflictWhere, ModifyTableState::operation, ModifyTable::operation, outerPlan, outerPlanState, palloc(), PlanState::plan, ModifyTableState::ps, PlanState::ps_ExprContext, PlanState::ps_ResultTupleSlot, WithCheckOption::qual, RelationData::rd_att, RelationData::rd_rel, RelationGetRelid, MTTargetRelLookup::relationIndex, ModifyTable::resultRelations, ModifyTableState::resultRelInfo, ModifyTable::returningLists, ResultRelInfo::ri_BatchSize, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_onConflict, ResultRelInfo::ri_onConflictArbiterIndexes, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_returningList, ResultRelInfo::ri_RootResultRelInfo, ResultRelInfo::ri_RowIdAttNo, ResultRelInfo::ri_usesFdwDirectModify, ResultRelInfo::ri_WithCheckOptionExprs, ResultRelInfo::ri_WithCheckOptions, ModifyTable::rootRelation, ModifyTableState::rootResultRelInfo, ModifyTable::rowMarks, PlanRowMark::rti, PlanState::state, table_slot_create(), Plan::targetlist, TTSOpsVirtual, and ModifyTable::withCheckOptionLists.

Referenced by ExecInitNode().

◆ ExecInitStoredGenerated()

void ExecInitStoredGenerated ( ResultRelInfo resultRelInfo,
EState estate,
CmdType  cmdtype 
)

Definition at line 346 of file nodeModifyTable.c.

349 {
350  Relation rel = resultRelInfo->ri_RelationDesc;
351  TupleDesc tupdesc = RelationGetDescr(rel);
352  int natts = tupdesc->natts;
353  ExprState **ri_GeneratedExprs;
354  int ri_NumGeneratedNeeded;
355  Bitmapset *updatedCols;
356  MemoryContext oldContext;
357 
358  /* Nothing to do if no generated columns */
359  if (!(tupdesc->constr && tupdesc->constr->has_generated_stored))
360  return;
361 
362  /*
363  * In an UPDATE, we can skip computing any generated columns that do not
364  * depend on any UPDATE target column. But if there is a BEFORE ROW
365  * UPDATE trigger, we cannot skip because the trigger might change more
366  * columns.
367  */
368  if (cmdtype == CMD_UPDATE &&
369  !(rel->trigdesc && rel->trigdesc->trig_update_before_row))
370  updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
371  else
372  updatedCols = NULL;
373 
374  /*
375  * Make sure these data structures are built in the per-query memory
376  * context so they'll survive throughout the query.
377  */
378  oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
379 
380  ri_GeneratedExprs = (ExprState **) palloc0(natts * sizeof(ExprState *));
381  ri_NumGeneratedNeeded = 0;
382 
383  for (int i = 0; i < natts; i++)
384  {
385  if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
386  {
387  Expr *expr;
388 
389  /* Fetch the GENERATED AS expression tree */
390  expr = (Expr *) build_column_default(rel, i + 1);
391  if (expr == NULL)
392  elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
393  i + 1, RelationGetRelationName(rel));
394 
395  /*
396  * If it's an update with a known set of update target columns,
397  * see if we can skip the computation.
398  */
399  if (updatedCols)
400  {
401  Bitmapset *attrs_used = NULL;
402 
403  pull_varattnos((Node *) expr, 1, &attrs_used);
404 
405  if (!bms_overlap(updatedCols, attrs_used))
406  continue; /* need not update this column */
407  }
408 
409  /* No luck, so prepare the expression for execution */
410  ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
411  ri_NumGeneratedNeeded++;
412 
413  /* If UPDATE, mark column in resultRelInfo->ri_extraUpdatedCols */
414  if (cmdtype == CMD_UPDATE)
415  resultRelInfo->ri_extraUpdatedCols =
416  bms_add_member(resultRelInfo->ri_extraUpdatedCols,
418  }
419  }
420 
421  /* Save in appropriate set of fields */
422  if (cmdtype == CMD_UPDATE)
423  {
424  /* Don't call twice */
425  Assert(resultRelInfo->ri_GeneratedExprsU == NULL);
426 
427  resultRelInfo->ri_GeneratedExprsU = ri_GeneratedExprs;
428  resultRelInfo->ri_NumGeneratedNeededU = ri_NumGeneratedNeeded;
429  }
430  else
431  {
432  /* Don't call twice */
433  Assert(resultRelInfo->ri_GeneratedExprsI == NULL);
434 
435  resultRelInfo->ri_GeneratedExprsI = ri_GeneratedExprs;
436  resultRelInfo->ri_NumGeneratedNeededI = ri_NumGeneratedNeeded;
437  }
438 
439  MemoryContextSwitchTo(oldContext);
440 }
Bitmapset * bms_add_member(Bitmapset *a, int x)
Definition: bitmapset.c:755
bool bms_overlap(const Bitmapset *a, const Bitmapset *b)
Definition: bitmapset.c:511
ExprState * ExecPrepareExpr(Expr *node, EState *estate)
Definition: execExpr.c:735
Bitmapset * ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate)
Definition: execUtils.c:1319
void * palloc0(Size size)
Definition: mcxt.c:1241
Node * build_column_default(Relation rel, int attrno)
TriggerDesc * trigdesc
Definition: rel.h:116
Bitmapset * ri_extraUpdatedCols
Definition: execnodes.h:468
bool trig_update_before_row
Definition: reltrigger.h:61
#define FirstLowInvalidHeapAttributeNumber
Definition: sysattr.h:27
void pull_varattnos(Node *node, Index varno, Bitmapset **varattnos)
Definition: var.c:291

References Assert(), bms_add_member(), bms_overlap(), build_column_default(), CMD_UPDATE, TupleDescData::constr, elog(), ERROR, EState::es_query_cxt, ExecGetUpdatedCols(), ExecPrepareExpr(), FirstLowInvalidHeapAttributeNumber, TupleConstr::has_generated_stored, i, MemoryContextSwitchTo(), TupleDescData::natts, palloc0(), pull_varattnos(), RelationGetDescr, RelationGetRelationName, ResultRelInfo::ri_extraUpdatedCols, ResultRelInfo::ri_GeneratedExprsI, ResultRelInfo::ri_GeneratedExprsU, ResultRelInfo::ri_NumGeneratedNeededI, ResultRelInfo::ri_NumGeneratedNeededU, ResultRelInfo::ri_RelationDesc, TriggerDesc::trig_update_before_row, RelationData::trigdesc, and TupleDescAttr.

Referenced by ExecComputeStoredGenerated(), and ExecGetExtraUpdatedCols().

◆ ExecInitUpdateProjection()

static void ExecInitUpdateProjection ( ModifyTableState mtstate,
ResultRelInfo resultRelInfo 
)
static

Definition at line 612 of file nodeModifyTable.c.

614 {
615  ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
616  Plan *subplan = outerPlan(node);
617  EState *estate = mtstate->ps.state;
618  TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
619  int whichrel;
620  List *updateColnos;
621 
622  /*
623  * Usually, mt_lastResultIndex matches the target rel. If it happens not
624  * to, we can get the index the hard way with an integer division.
625  */
626  whichrel = mtstate->mt_lastResultIndex;
627  if (resultRelInfo != mtstate->resultRelInfo + whichrel)
628  {
629  whichrel = resultRelInfo - mtstate->resultRelInfo;
630  Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
631  }
632 
633  updateColnos = (List *) list_nth(node->updateColnosLists, whichrel);
634 
635  /*
636  * For UPDATE, we use the old tuple to fill up missing values in the tuple
637  * produced by the subplan to get the new tuple. We need two slots, both
638  * matching the table's desired format.
639  */
640  resultRelInfo->ri_oldTupleSlot =
641  table_slot_create(resultRelInfo->ri_RelationDesc,
642  &estate->es_tupleTable);
643  resultRelInfo->ri_newTupleSlot =
644  table_slot_create(resultRelInfo->ri_RelationDesc,
645  &estate->es_tupleTable);
646 
647  /* need an expression context to do the projection */
648  if (mtstate->ps.ps_ExprContext == NULL)
649  ExecAssignExprContext(estate, &mtstate->ps);
650 
651  resultRelInfo->ri_projectNew =
652  ExecBuildUpdateProjection(subplan->targetlist,
653  false, /* subplan did the evaluation */
654  updateColnos,
655  relDesc,
656  mtstate->ps.ps_ExprContext,
657  resultRelInfo->ri_newTupleSlot,
658  &mtstate->ps);
659 
660  resultRelInfo->ri_projectNewInfoValid = true;
661 }
List * updateColnosLists
Definition: plannodes.h:241

References Assert(), EState::es_tupleTable, ExecAssignExprContext(), ExecBuildUpdateProjection(), list_nth(), ModifyTableState::mt_lastResultIndex, outerPlan, PlanState::plan, ModifyTableState::ps, PlanState::ps_ExprContext, RelationGetDescr, ModifyTableState::resultRelInfo, ResultRelInfo::ri_newTupleSlot, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_projectNew, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_RelationDesc, PlanState::state, table_slot_create(), and ModifyTable::updateColnosLists.

Referenced by ExecCrossPartitionUpdate(), ExecModifyTable(), and ExecUpdate().

◆ ExecInsert()

static TupleTableSlot* ExecInsert ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
TupleTableSlot slot,
bool  canSetTag,
TupleTableSlot **  inserted_tuple,
ResultRelInfo **  insert_destrel 
)
static

Definition at line 752 of file nodeModifyTable.c.

758 {
759  ModifyTableState *mtstate = context->mtstate;
760  EState *estate = context->estate;
761  Relation resultRelationDesc;
762  List *recheckIndexes = NIL;
763  TupleTableSlot *planSlot = context->planSlot;
764  TupleTableSlot *result = NULL;
765  TransitionCaptureState *ar_insert_trig_tcs;
766  ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
767  OnConflictAction onconflict = node->onConflictAction;
769  MemoryContext oldContext;
770 
771  /*
772  * If the input result relation is a partitioned table, find the leaf
773  * partition to insert the tuple into.
774  */
775  if (proute)
776  {
777  ResultRelInfo *partRelInfo;
778 
779  slot = ExecPrepareTupleRouting(mtstate, estate, proute,
780  resultRelInfo, slot,
781  &partRelInfo);
782  resultRelInfo = partRelInfo;
783  }
784 
785  ExecMaterializeSlot(slot);
786 
787  resultRelationDesc = resultRelInfo->ri_RelationDesc;
788 
789  /*
790  * Open the table's indexes, if we have not done so already, so that we
791  * can add new index entries for the inserted tuple.
792  */
793  if (resultRelationDesc->rd_rel->relhasindex &&
794  resultRelInfo->ri_IndexRelationDescs == NULL)
795  ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
796 
797  /*
798  * BEFORE ROW INSERT Triggers.
799  *
800  * Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an
801  * INSERT ... ON CONFLICT statement. We cannot check for constraint
802  * violations before firing these triggers, because they can change the
803  * values to insert. Also, they can run arbitrary user-defined code with
804  * side-effects that we can't cancel by just not inserting the tuple.
805  */
806  if (resultRelInfo->ri_TrigDesc &&
807  resultRelInfo->ri_TrigDesc->trig_insert_before_row)
808  {
809  /* Flush any pending inserts, so rows are visible to the triggers */
811  ExecPendingInserts(estate);
812 
813  if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
814  return NULL; /* "do nothing" */
815  }
816 
817  /* INSTEAD OF ROW INSERT Triggers */
818  if (resultRelInfo->ri_TrigDesc &&
819  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)
820  {
821  if (!ExecIRInsertTriggers(estate, resultRelInfo, slot))
822  return NULL; /* "do nothing" */
823  }
824  else if (resultRelInfo->ri_FdwRoutine)
825  {
826  /*
827  * GENERATED expressions might reference the tableoid column, so
828  * (re-)initialize tts_tableOid before evaluating them.
829  */
830  slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
831 
832  /*
833  * Compute stored generated columns
834  */
835  if (resultRelationDesc->rd_att->constr &&
836  resultRelationDesc->rd_att->constr->has_generated_stored)
837  ExecComputeStoredGenerated(resultRelInfo, estate, slot,
838  CMD_INSERT);
839 
840  /*
841  * If the FDW supports batching, and batching is requested, accumulate
842  * rows and insert them in batches. Otherwise use the per-row inserts.
843  */
844  if (resultRelInfo->ri_BatchSize > 1)
845  {
846  bool flushed = false;
847 
848  /*
849  * When we've reached the desired batch size, perform the
850  * insertion.
851  */
852  if (resultRelInfo->ri_NumSlots == resultRelInfo->ri_BatchSize)
853  {
854  ExecBatchInsert(mtstate, resultRelInfo,
855  resultRelInfo->ri_Slots,
856  resultRelInfo->ri_PlanSlots,
857  resultRelInfo->ri_NumSlots,
858  estate, canSetTag);
859  resultRelInfo->ri_NumSlots = 0;
860  flushed = true;
861  }
862 
863  oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
864 
865  if (resultRelInfo->ri_Slots == NULL)
866  {
867  resultRelInfo->ri_Slots = palloc(sizeof(TupleTableSlot *) *
868  resultRelInfo->ri_BatchSize);
869  resultRelInfo->ri_PlanSlots = palloc(sizeof(TupleTableSlot *) *
870  resultRelInfo->ri_BatchSize);
871  }
872 
873  /*
874  * Initialize the batch slots. We don't know how many slots will
875  * be needed, so we initialize them as the batch grows, and we
876  * keep them across batches. To mitigate an inefficiency in how
877  * resource owner handles objects with many references (as with
878  * many slots all referencing the same tuple descriptor) we copy
879  * the appropriate tuple descriptor for each slot.
880  */
881  if (resultRelInfo->ri_NumSlots >= resultRelInfo->ri_NumSlotsInitialized)
882  {
884  TupleDesc plan_tdesc =
886 
887  resultRelInfo->ri_Slots[resultRelInfo->ri_NumSlots] =
888  MakeSingleTupleTableSlot(tdesc, slot->tts_ops);
889 
890  resultRelInfo->ri_PlanSlots[resultRelInfo->ri_NumSlots] =
891  MakeSingleTupleTableSlot(plan_tdesc, planSlot->tts_ops);
892 
893  /* remember how many batch slots we initialized */
894  resultRelInfo->ri_NumSlotsInitialized++;
895  }
896 
897  ExecCopySlot(resultRelInfo->ri_Slots[resultRelInfo->ri_NumSlots],
898  slot);
899 
900  ExecCopySlot(resultRelInfo->ri_PlanSlots[resultRelInfo->ri_NumSlots],
901  planSlot);
902 
903  /*
904  * If these are the first tuples stored in the buffers, add the
905  * target rel and the mtstate to the
906  * es_insert_pending_result_relations and
907  * es_insert_pending_modifytables lists respectively, execpt in
908  * the case where flushing was done above, in which case they
909  * would already have been added to the lists, so no need to do
910  * this.
911  */
912  if (resultRelInfo->ri_NumSlots == 0 && !flushed)
913  {
915  resultRelInfo));
918  resultRelInfo);
920  lappend(estate->es_insert_pending_modifytables, mtstate);
921  }
923  resultRelInfo));
924 
925  resultRelInfo->ri_NumSlots++;
926 
927  MemoryContextSwitchTo(oldContext);
928 
929  return NULL;
930  }
931 
932  /*
933  * insert into foreign table: let the FDW do it
934  */
935  slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
936  resultRelInfo,
937  slot,
938  planSlot);
939 
940  if (slot == NULL) /* "do nothing" */
941  return NULL;
942 
943  /*
944  * AFTER ROW Triggers or RETURNING expressions might reference the
945  * tableoid column, so (re-)initialize tts_tableOid before evaluating
946  * them. (This covers the case where the FDW replaced the slot.)
947  */
948  slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
949  }
950  else
951  {
952  WCOKind wco_kind;
953 
954  /*
955  * Constraints and GENERATED expressions might reference the tableoid
956  * column, so (re-)initialize tts_tableOid before evaluating them.
957  */
958  slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
959 
960  /*
961  * Compute stored generated columns
962  */
963  if (resultRelationDesc->rd_att->constr &&
964  resultRelationDesc->rd_att->constr->has_generated_stored)
965  ExecComputeStoredGenerated(resultRelInfo, estate, slot,
966  CMD_INSERT);
967 
968  /*
969  * Check any RLS WITH CHECK policies.
970  *
971  * Normally we should check INSERT policies. But if the insert is the
972  * result of a partition key update that moved the tuple to a new
973  * partition, we should instead check UPDATE policies, because we are
974  * executing policies defined on the target table, and not those
975  * defined on the child partitions.
976  *
977  * If we're running MERGE, we refer to the action that we're executing
978  * to know if we're doing an INSERT or UPDATE to a partition table.
979  */
980  if (mtstate->operation == CMD_UPDATE)
981  wco_kind = WCO_RLS_UPDATE_CHECK;
982  else if (mtstate->operation == CMD_MERGE)
983  wco_kind = (context->relaction->mas_action->commandType == CMD_UPDATE) ?
985  else
986  wco_kind = WCO_RLS_INSERT_CHECK;
987 
988  /*
989  * ExecWithCheckOptions() will skip any WCOs which are not of the kind
990  * we are looking for at this point.
991  */
992  if (resultRelInfo->ri_WithCheckOptions != NIL)
993  ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
994 
995  /*
996  * Check the constraints of the tuple.
997  */
998  if (resultRelationDesc->rd_att->constr)
999  ExecConstraints(resultRelInfo, slot, estate);
1000 
1001  /*
1002  * Also check the tuple against the partition constraint, if there is
1003  * one; except that if we got here via tuple-routing, we don't need to
1004  * if there's no BR trigger defined on the partition.
1005  */
1006  if (resultRelationDesc->rd_rel->relispartition &&
1007  (resultRelInfo->ri_RootResultRelInfo == NULL ||
1008  (resultRelInfo->ri_TrigDesc &&
1009  resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
1010  ExecPartitionCheck(resultRelInfo, slot, estate, true);
1011 
1012  if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
1013  {
1014  /* Perform a speculative insertion. */
1015  uint32 specToken;
1016  ItemPointerData conflictTid;
1017  bool specConflict;
1018  List *arbiterIndexes;
1019 
1020  arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
1021 
1022  /*
1023  * Do a non-conclusive check for conflicts first.
1024  *
1025  * We're not holding any locks yet, so this doesn't guarantee that
1026  * the later insert won't conflict. But it avoids leaving behind
1027  * a lot of canceled speculative insertions, if you run a lot of
1028  * INSERT ON CONFLICT statements that do conflict.
1029  *
1030  * We loop back here if we find a conflict below, either during
1031  * the pre-check, or when we re-check after inserting the tuple
1032  * speculatively. Better allow interrupts in case some bug makes
1033  * this an infinite loop.
1034  */
1035  vlock:
1037  specConflict = false;
1038  if (!ExecCheckIndexConstraints(resultRelInfo, slot, estate,
1039  &conflictTid, arbiterIndexes))
1040  {
1041  /* committed conflict tuple found */
1042  if (onconflict == ONCONFLICT_UPDATE)
1043  {
1044  /*
1045  * In case of ON CONFLICT DO UPDATE, execute the UPDATE
1046  * part. Be prepared to retry if the UPDATE fails because
1047  * of another concurrent UPDATE/DELETE to the conflict
1048  * tuple.
1049  */
1050  TupleTableSlot *returning = NULL;
1051 
1052  if (ExecOnConflictUpdate(context, resultRelInfo,
1053  &conflictTid, slot, canSetTag,
1054  &returning))
1055  {
1056  InstrCountTuples2(&mtstate->ps, 1);
1057  return returning;
1058  }
1059  else
1060  goto vlock;
1061  }
1062  else
1063  {
1064  /*
1065  * In case of ON CONFLICT DO NOTHING, do nothing. However,
1066  * verify that the tuple is visible to the executor's MVCC
1067  * snapshot at higher isolation levels.
1068  *
1069  * Using ExecGetReturningSlot() to store the tuple for the
1070  * recheck isn't that pretty, but we can't trivially use
1071  * the input slot, because it might not be of a compatible
1072  * type. As there's no conflicting usage of
1073  * ExecGetReturningSlot() in the DO NOTHING case...
1074  */
1075  Assert(onconflict == ONCONFLICT_NOTHING);
1076  ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid,
1077  ExecGetReturningSlot(estate, resultRelInfo));
1078  InstrCountTuples2(&mtstate->ps, 1);
1079  return NULL;
1080  }
1081  }
1082 
1083  /*
1084  * Before we start insertion proper, acquire our "speculative
1085  * insertion lock". Others can use that to wait for us to decide
1086  * if we're going to go ahead with the insertion, instead of
1087  * waiting for the whole transaction to complete.
1088  */
1090 
1091  /* insert the tuple, with the speculative token */
1092  table_tuple_insert_speculative(resultRelationDesc, slot,
1093  estate->es_output_cid,
1094  0,
1095  NULL,
1096  specToken);
1097 
1098  /* insert index entries for tuple */
1099  recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
1100  slot, estate, false, true,
1101  &specConflict,
1102  arbiterIndexes,
1103  false);
1104 
1105  /* adjust the tuple's state accordingly */
1106  table_tuple_complete_speculative(resultRelationDesc, slot,
1107  specToken, !specConflict);
1108 
1109  /*
1110  * Wake up anyone waiting for our decision. They will re-check
1111  * the tuple, see that it's no longer speculative, and wait on our
1112  * XID as if this was a regularly inserted tuple all along. Or if
1113  * we killed the tuple, they will see it's dead, and proceed as if
1114  * the tuple never existed.
1115  */
1117 
1118  /*
1119  * If there was a conflict, start from the beginning. We'll do
1120  * the pre-check again, which will now find the conflicting tuple
1121  * (unless it aborts before we get there).
1122  */
1123  if (specConflict)
1124  {
1125  list_free(recheckIndexes);
1126  goto vlock;
1127  }
1128 
1129  /* Since there was no insertion conflict, we're done */
1130  }
1131  else
1132  {
1133  /* insert the tuple normally */
1134  table_tuple_insert(resultRelationDesc, slot,
1135  estate->es_output_cid,
1136  0, NULL);
1137 
1138  /* insert index entries for tuple */
1139  if (resultRelInfo->ri_NumIndices > 0)
1140  recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
1141  slot, estate, false,
1142  false, NULL, NIL,
1143  false);
1144  }
1145  }
1146 
1147  if (canSetTag)
1148  (estate->es_processed)++;
1149 
1150  /*
1151  * If this insert is the result of a partition key update that moved the
1152  * tuple to a new partition, put this row into the transition NEW TABLE,
1153  * if there is one. We need to do this separately for DELETE and INSERT
1154  * because they happen on different tables.
1155  */
1156  ar_insert_trig_tcs = mtstate->mt_transition_capture;
1157  if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
1159  {
1160  ExecARUpdateTriggers(estate, resultRelInfo,
1161  NULL, NULL,
1162  NULL,
1163  NULL,
1164  slot,
1165  NULL,
1166  mtstate->mt_transition_capture,
1167  false);
1168 
1169  /*
1170  * We've already captured the NEW TABLE row, so make sure any AR
1171  * INSERT trigger fired below doesn't capture it again.
1172  */
1173  ar_insert_trig_tcs = NULL;
1174  }
1175 
1176  /* AFTER ROW INSERT Triggers */
1177  ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
1178  ar_insert_trig_tcs);
1179 
1180  list_free(recheckIndexes);
1181 
1182  /*
1183  * Check any WITH CHECK OPTION constraints from parent views. We are
1184  * required to do this after testing all constraints and uniqueness
1185  * violations per the SQL spec, so we do it after actually inserting the
1186  * record into the heap and all indexes.
1187  *
1188  * ExecWithCheckOptions will elog(ERROR) if a violation is found, so the
1189  * tuple will never be seen, if it violates the WITH CHECK OPTION.
1190  *
1191  * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
1192  * are looking for at this point.
1193  */
1194  if (resultRelInfo->ri_WithCheckOptions != NIL)
1195  ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
1196 
1197  /* Process RETURNING if present */
1198  if (resultRelInfo->ri_projectReturning)
1199  result = ExecProcessReturning(resultRelInfo, slot, planSlot);
1200 
1201  if (inserted_tuple)
1202  *inserted_tuple = slot;
1203  if (insert_destrel)
1204  *insert_destrel = resultRelInfo;
1205 
1206  return result;
1207 }
unsigned int uint32
Definition: c.h:490
bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, List *arbiterIndexes)
Definition: execIndexing.c:522
List * ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, bool update, bool noDupErr, bool *specConflict, List *arbiterIndexes, bool onlySummarizing)
Definition: execIndexing.c:293
void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
Definition: execIndexing.c:156
bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, bool emitError)
Definition: execMain.c:1779
void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
Definition: execMain.c:1903
TupleTableSlot * MakeSingleTupleTableSlot(TupleDesc tupdesc, const TupleTableSlotOps *tts_ops)
Definition: execTuples.c:1238
#define InstrCountTuples2(node, delta)
Definition: execnodes.h:1134
bool list_member_ptr(const List *list, const void *datum)
Definition: list.c:681
void list_free(List *list)
Definition: list.c:1545
uint32 SpeculativeInsertionLockAcquire(TransactionId xid)
Definition: lmgr.c:783
void SpeculativeInsertionLockRelease(TransactionId xid)
Definition: lmgr.c:809
#define CHECK_FOR_INTERRUPTS()
Definition: miscadmin.h:121
static void ExecCheckTIDVisible(EState *estate, ResultRelInfo *relinfo, ItemPointer tid, TupleTableSlot *tempSlot)
void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, CmdType cmdtype)
static TupleTableSlot * ExecPrepareTupleRouting(ModifyTableState *mtstate, EState *estate, PartitionTupleRouting *proute, ResultRelInfo *targetRelInfo, TupleTableSlot *slot, ResultRelInfo **partRelInfo)
static bool ExecOnConflictUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer conflictTid, TupleTableSlot *excludedSlot, bool canSetTag, TupleTableSlot **returning)
static void ExecBatchInsert(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int numSlots, EState *estate, bool canSetTag)
OnConflictAction
Definition: nodes.h:425
@ ONCONFLICT_NOTHING
Definition: nodes.h:427
WCOKind
Definition: parsenodes.h:1313
@ WCO_RLS_INSERT_CHECK
Definition: parsenodes.h:1315
@ WCO_RLS_UPDATE_CHECK
Definition: parsenodes.h:1316
List * es_insert_pending_modifytables
Definition: execnodes.h:714
ExecForeignInsert_function ExecForeignInsert
Definition: fdwapi.h:232
CmdType commandType
Definition: parsenodes.h:1691
int ri_NumIndices
Definition: execnodes.h:453
RelationPtr ri_IndexRelationDescs
Definition: execnodes.h:456
bool trig_insert_instead_row
Definition: reltrigger.h:58
bool trig_insert_before_row
Definition: reltrigger.h:56
TupleDesc tts_tupleDescriptor
Definition: tuptable.h:124
static void table_tuple_complete_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken, bool succeeded)
Definition: tableam.h:1432
static void table_tuple_insert(Relation rel, TupleTableSlot *slot, CommandId cid, int options, struct BulkInsertStateData *bistate)
Definition: tableam.h:1399
static void table_tuple_insert_speculative(Relation rel, TupleTableSlot *slot, CommandId cid, int options, struct BulkInsertStateData *bistate, uint32 specToken)
Definition: tableam.h:1418
bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot)
Definition: trigger.c:2469
bool ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot)
Definition: trigger.c:2562
TupleDesc CreateTupleDescCopy(TupleDesc tupdesc)
Definition: tupdesc.c:111
TransactionId GetCurrentTransactionId(void)
Definition: xact.c:445

References Assert(), CHECK_FOR_INTERRUPTS, CMD_INSERT, CMD_MERGE, CMD_UPDATE, MergeAction::commandType, TupleDescData::constr, CreateTupleDescCopy(), EState::es_insert_pending_modifytables, EState::es_insert_pending_result_relations, EState::es_output_cid, EState::es_processed, EState::es_query_cxt, ModifyTableContext::estate, ExecARInsertTriggers(), ExecARUpdateTriggers(), ExecBatchInsert(), ExecBRInsertTriggers(), ExecCheckIndexConstraints(), ExecCheckTIDVisible(), ExecComputeStoredGenerated(), ExecConstraints(), ExecCopySlot(), FdwRoutine::ExecForeignInsert, ExecGetReturningSlot(), ExecInsertIndexTuples(), ExecIRInsertTriggers(), ExecMaterializeSlot(), ExecOnConflictUpdate(), ExecOpenIndices(), ExecPartitionCheck(), ExecPendingInserts(), ExecPrepareTupleRouting(), ExecProcessReturning(), ExecWithCheckOptions(), GetCurrentTransactionId(), TupleConstr::has_generated_stored, if(), InstrCountTuples2, lappend(), list_free(), list_member_ptr(), MakeSingleTupleTableSlot(), MergeActionState::mas_action, MemoryContextSwitchTo(), ModifyTableState::mt_partition_tuple_routing, ModifyTableState::mt_transition_capture, ModifyTableContext::mtstate, NIL, ONCONFLICT_NONE, ONCONFLICT_NOTHING, ONCONFLICT_UPDATE, ModifyTable::onConflictAction, ModifyTableState::operation, palloc(), PlanState::plan, ModifyTableContext::planSlot, ModifyTableState::ps, RelationData::rd_att, RelationData::rd_rel, ModifyTableContext::relaction, RelationGetRelid, ResultRelInfo::ri_BatchSize, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_IndexRelationDescs, ResultRelInfo::ri_NumIndices, ResultRelInfo::ri_NumSlots, ResultRelInfo::ri_NumSlotsInitialized, ResultRelInfo::ri_onConflictArbiterIndexes, ResultRelInfo::ri_PlanSlots, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_RootResultRelInfo, ResultRelInfo::ri_Slots, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_WithCheckOptions, SpeculativeInsertionLockAcquire(), SpeculativeInsertionLockRelease(), table_tuple_complete_speculative(), table_tuple_insert(), table_tuple_insert_speculative(), TransitionCaptureState::tcs_update_new_table, TriggerDesc::trig_insert_before_row, TriggerDesc::trig_insert_instead_row, TupleTableSlot::tts_ops, TupleTableSlot::tts_tableOid, TupleTableSlot::tts_tupleDescriptor, WCO_RLS_INSERT_CHECK, WCO_RLS_UPDATE_CHECK, and WCO_VIEW_CHECK.

Referenced by ExecCrossPartitionUpdate(), ExecMergeNotMatched(), and ExecModifyTable().

◆ ExecLookupResultRelByOid()

ResultRelInfo* ExecLookupResultRelByOid ( ModifyTableState node,
Oid  resultoid,
bool  missing_ok,
bool  update_cache 
)

Definition at line 3848 of file nodeModifyTable.c.

3850 {
3851  if (node->mt_resultOidHash)
3852  {
3853  /* Use the pre-built hash table to locate the rel */
3854  MTTargetRelLookup *mtlookup;
3855 
3856  mtlookup = (MTTargetRelLookup *)
3857  hash_search(node->mt_resultOidHash, &resultoid, HASH_FIND, NULL);
3858  if (mtlookup)
3859  {
3860  if (update_cache)
3861  {
3862  node->mt_lastResultOid = resultoid;
3863  node->mt_lastResultIndex = mtlookup->relationIndex;
3864  }
3865  return node->resultRelInfo + mtlookup->relationIndex;
3866  }
3867  }
3868  else
3869  {
3870  /* With few target rels, just search the ResultRelInfo array */
3871  for (int ndx = 0; ndx < node->mt_nrels; ndx++)
3872  {
3873  ResultRelInfo *rInfo = node->resultRelInfo + ndx;
3874 
3875  if (RelationGetRelid(rInfo->ri_RelationDesc) == resultoid)
3876  {
3877  if (update_cache)
3878  {
3879  node->mt_lastResultOid = resultoid;
3880  node->mt_lastResultIndex = ndx;
3881  }
3882  return rInfo;
3883  }
3884  }
3885  }
3886 
3887  if (!missing_ok)
3888  elog(ERROR, "incorrect result relation OID %u", resultoid);
3889  return NULL;
3890 }
@ HASH_FIND
Definition: hsearch.h:113

References elog(), ERROR, HASH_FIND, hash_search(), ModifyTableState::mt_lastResultIndex, ModifyTableState::mt_lastResultOid, ModifyTableState::mt_nrels, ModifyTableState::mt_resultOidHash, RelationGetRelid, MTTargetRelLookup::relationIndex, ModifyTableState::resultRelInfo, and ResultRelInfo::ri_RelationDesc.

Referenced by ExecFindPartition(), and ExecModifyTable().

◆ ExecMerge()

static TupleTableSlot * ExecMerge ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
bool  canSetTag 
)
static

Definition at line 2683 of file nodeModifyTable.c.

2685 {
2686  bool matched;
2687 
2688  /*-----
2689  * If we are dealing with a WHEN MATCHED case (tupleid is valid), we
2690  * execute the first action for which the additional WHEN MATCHED AND
2691  * quals pass. If an action without quals is found, that action is
2692  * executed.
2693  *
2694  * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at
2695  * the given WHEN NOT MATCHED actions in sequence until one passes.
2696  *
2697  * Things get interesting in case of concurrent update/delete of the
2698  * target tuple. Such concurrent update/delete is detected while we are
2699  * executing a WHEN MATCHED action.
2700  *
2701  * A concurrent update can:
2702  *
2703  * 1. modify the target tuple so that it no longer satisfies the
2704  * additional quals attached to the current WHEN MATCHED action
2705  *
2706  * In this case, we are still dealing with a WHEN MATCHED case.
2707  * We recheck the list of WHEN MATCHED actions from the start and
2708  * choose the first one that satisfies the new target tuple.
2709  *
2710  * 2. modify the target tuple so that the join quals no longer pass and
2711  * hence the source tuple no longer has a match.
2712  *
2713  * In this case, the source tuple no longer matches the target tuple,
2714  * so we now instead find a qualifying WHEN NOT MATCHED action to
2715  * execute.
2716  *
2717  * XXX Hmmm, what if the updated tuple would now match one that was
2718  * considered NOT MATCHED so far?
2719  *
2720  * A concurrent delete changes a WHEN MATCHED case to WHEN NOT MATCHED.
2721  *
2722  * ExecMergeMatched takes care of following the update chain and
2723  * re-finding the qualifying WHEN MATCHED action, as long as the updated
2724  * target tuple still satisfies the join quals, i.e., it remains a WHEN
2725  * MATCHED case. If the tuple gets deleted or the join quals fail, it
2726  * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
2727  * always make progress by following the update chain and we never switch
2728  * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
2729  * livelock.
2730  */
2731  matched = tupleid != NULL;
2732  if (matched)
2733  matched = ExecMergeMatched(context, resultRelInfo, tupleid, canSetTag);
2734 
2735  /*
2736  * Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched()
2737  * returned "false", indicating the previously MATCHED tuple no longer
2738  * matches.
2739  */
2740  if (!matched)
2741  ExecMergeNotMatched(context, resultRelInfo, canSetTag);
2742 
2743  /* No RETURNING support yet */
2744  return NULL;
2745 }
static void ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, bool canSetTag)
static bool ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool canSetTag)

References ExecMergeMatched(), and ExecMergeNotMatched().

Referenced by ExecModifyTable().

◆ ExecMergeMatched()

static bool ExecMergeMatched ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
bool  canSetTag 
)
static

Definition at line 2769 of file nodeModifyTable.c.

2771 {
2772  ModifyTableState *mtstate = context->mtstate;
2773  TupleTableSlot *newslot;
2774  EState *estate = context->estate;
2775  ExprContext *econtext = mtstate->ps.ps_ExprContext;
2776  bool isNull;
2777  EPQState *epqstate = &mtstate->mt_epqstate;
2778  ListCell *l;
2779 
2780  /*
2781  * If there are no WHEN MATCHED actions, we are done.
2782  */
2783  if (resultRelInfo->ri_matchedMergeAction == NIL)
2784  return true;
2785 
2786  /*
2787  * Make tuple and any needed join variables available to ExecQual and
2788  * ExecProject. The target's existing tuple is installed in the scantuple.
2789  * Again, this target relation's slot is required only in the case of a
2790  * MATCHED tuple and UPDATE/DELETE actions.
2791  */
2792  econtext->ecxt_scantuple = resultRelInfo->ri_oldTupleSlot;
2793  econtext->ecxt_innertuple = context->planSlot;
2794  econtext->ecxt_outertuple = NULL;
2795 
2796 lmerge_matched:
2797 
2798  /*
2799  * This routine is only invoked for matched rows, and we must have found
2800  * the tupleid of the target row in that case; fetch that tuple.
2801  *
2802  * We use SnapshotAny for this because we might get called again after
2803  * EvalPlanQual returns us a new tuple, which may not be visible to our
2804  * MVCC snapshot.
2805  */
2806 
2807  if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
2808  tupleid,
2809  SnapshotAny,
2810  resultRelInfo->ri_oldTupleSlot))
2811  elog(ERROR, "failed to fetch the target tuple");
2812 
2813  foreach(l, resultRelInfo->ri_matchedMergeAction)
2814  {
2815  MergeActionState *relaction = (MergeActionState *) lfirst(l);
2816  CmdType commandType = relaction->mas_action->commandType;
2817  TM_Result result;
2818  UpdateContext updateCxt = {0};
2819 
2820  /*
2821  * Test condition, if any.
2822  *
2823  * In the absence of any condition, we perform the action
2824  * unconditionally (no need to check separately since ExecQual() will
2825  * return true if there are no conditions to evaluate).
2826  */
2827  if (!ExecQual(relaction->mas_whenqual, econtext))
2828  continue;
2829 
2830  /*
2831  * Check if the existing target tuple meets the USING checks of
2832  * UPDATE/DELETE RLS policies. If those checks fail, we throw an
2833  * error.
2834  *
2835  * The WITH CHECK quals are applied in ExecUpdate() and hence we need
2836  * not do anything special to handle them.
2837  *
2838  * NOTE: We must do this after WHEN quals are evaluated, so that we
2839  * check policies only when they matter.
2840  */
2841  if (resultRelInfo->ri_WithCheckOptions)
2842  {
2843  ExecWithCheckOptions(commandType == CMD_UPDATE ?
2845  resultRelInfo,
2846  resultRelInfo->ri_oldTupleSlot,
2847  context->mtstate->ps.state);
2848  }
2849 
2850  /* Perform stated action */
2851  switch (commandType)
2852  {
2853  case CMD_UPDATE:
2854 
2855  /*
2856  * Project the output tuple, and use that to update the table.
2857  * We don't need to filter out junk attributes, because the
2858  * UPDATE action's targetlist doesn't have any.
2859  */
2860  newslot = ExecProject(relaction->mas_proj);
2861 
2862  context->relaction = relaction;
2863  if (!ExecUpdatePrologue(context, resultRelInfo,
2864  tupleid, NULL, newslot, &result))
2865  {
2866  if (result == TM_Ok)
2867  return true; /* "do nothing" */
2868  break; /* concurrent update/delete */
2869  }
2870  result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
2871  newslot, false, false, &updateCxt);
2872  if (result == TM_Ok && updateCxt.updated)
2873  {
2874  ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
2875  tupleid, NULL, newslot);
2876  mtstate->mt_merge_updated += 1;
2877  }
2878  break;
2879 
2880  case CMD_DELETE:
2881  context->relaction = relaction;
2882  if (!ExecDeletePrologue(context, resultRelInfo, tupleid,
2883  NULL, NULL, &result))
2884  {
2885  if (result == TM_Ok)
2886  return true; /* "do nothing" */
2887  break; /* concurrent update/delete */
2888  }
2889  result = ExecDeleteAct(context, resultRelInfo, tupleid,
2890  false, false);
2891  if (result == TM_Ok)
2892  {
2893  ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
2894  false);
2895  mtstate->mt_merge_deleted += 1;
2896  }
2897  break;
2898 
2899  case CMD_NOTHING:
2900  /* Doing nothing is always OK */
2901  result = TM_Ok;
2902  break;
2903 
2904  default:
2905  elog(ERROR, "unknown action in MERGE WHEN MATCHED clause");
2906  }
2907 
2908  switch (result)
2909  {
2910  case TM_Ok:
2911  /* all good; perform final actions */
2912  if (canSetTag && commandType != CMD_NOTHING)
2913  (estate->es_processed)++;
2914 
2915  break;
2916 
2917  case TM_SelfModified:
2918 
2919  /*
2920  * The SQL standard disallows this for MERGE.
2921  */
2923  ereport(ERROR,
2924  (errcode(ERRCODE_CARDINALITY_VIOLATION),
2925  /* translator: %s is a SQL command name */
2926  errmsg("%s command cannot affect row a second time",
2927  "MERGE"),
2928  errhint("Ensure that not more than one source row matches any one target row.")));
2929  /* This shouldn't happen */
2930  elog(ERROR, "attempted to update or delete invisible tuple");
2931  break;
2932 
2933  case TM_Deleted:
2935  ereport(ERROR,
2937  errmsg("could not serialize access due to concurrent delete")));
2938 
2939  /*
2940  * If the tuple was already deleted, return to let caller
2941  * handle it under NOT MATCHED clauses.
2942  */
2943  return false;
2944 
2945  case TM_Updated:
2946  {
2947  Relation resultRelationDesc;
2948  TupleTableSlot *epqslot,
2949  *inputslot;
2950  LockTupleMode lockmode;
2951 
2952  /*
2953  * The target tuple was concurrently updated by some other
2954  * transaction. Run EvalPlanQual() with the new version of
2955  * the tuple. If it does not return a tuple, then we
2956  * switch to the NOT MATCHED list of actions. If it does
2957  * return a tuple and the join qual is still satisfied,
2958  * then we just need to recheck the MATCHED actions,
2959  * starting from the top, and execute the first qualifying
2960  * action.
2961  */
2962  resultRelationDesc = resultRelInfo->ri_RelationDesc;
2963  lockmode = ExecUpdateLockMode(estate, resultRelInfo);
2964 
2965  inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc,
2966  resultRelInfo->ri_RangeTableIndex);
2967 
2968  result = table_tuple_lock(resultRelationDesc, tupleid,
2969  estate->es_snapshot,
2970  inputslot, estate->es_output_cid,
2971  lockmode, LockWaitBlock,
2973  &context->tmfd);
2974  switch (result)
2975  {
2976  case TM_Ok:
2977  epqslot = EvalPlanQual(epqstate,
2978  resultRelationDesc,
2979  resultRelInfo->ri_RangeTableIndex,
2980  inputslot);
2981 
2982  /*
2983  * If we got no tuple, or the tuple we get has a
2984  * NULL ctid, go back to caller: this one is not a
2985  * MATCHED tuple anymore, so they can retry with
2986  * NOT MATCHED actions.
2987  */
2988  if (TupIsNull(epqslot))
2989  return false;
2990 
2991  (void) ExecGetJunkAttribute(epqslot,
2992  resultRelInfo->ri_RowIdAttNo,
2993  &isNull);
2994  if (isNull)
2995  return false;
2996 
2997  /*
2998  * When a tuple was updated and migrated to
2999  * another partition concurrently, the current
3000  * MERGE implementation can't follow. There's
3001  * probably a better way to handle this case, but
3002  * it'd require recognizing the relation to which
3003  * the tuple moved, and setting our current
3004  * resultRelInfo to that.
3005  */
3007  ereport(ERROR,
3009  errmsg("tuple to be deleted was already moved to another partition due to concurrent update")));
3010 
3011  /*
3012  * A non-NULL ctid means that we are still dealing
3013  * with MATCHED case. Restart the loop so that we
3014  * apply all the MATCHED rules again, to ensure
3015  * that the first qualifying WHEN MATCHED action
3016  * is executed.
3017  *
3018  * Update tupleid to that of the new tuple, for
3019  * the refetch we do at the top.
3020  */
3021  ItemPointerCopy(&context->tmfd.ctid, tupleid);
3022  goto lmerge_matched;
3023 
3024  case TM_Deleted:
3025 
3026  /*
3027  * tuple already deleted; tell caller to run NOT
3028  * MATCHED actions
3029  */
3030  return false;
3031 
3032  case TM_SelfModified:
3033 
3034  /*
3035  * This can be reached when following an update
3036  * chain from a tuple updated by another session,
3037  * reaching a tuple that was already updated in
3038  * this transaction. If previously modified by
3039  * this command, ignore the redundant update,
3040  * otherwise error out.
3041  *
3042  * See also response to TM_SelfModified in
3043  * ExecUpdate().
3044  */
3045  if (context->tmfd.cmax != estate->es_output_cid)
3046  ereport(ERROR,
3047  (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
3048  errmsg("tuple to be updated or deleted was already modified by an operation triggered by the current command"),
3049  errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
3050  return false;
3051 
3052  default:
3053  /* see table_tuple_lock call in ExecDelete() */
3054  elog(ERROR, "unexpected table_tuple_lock status: %u",
3055  result);
3056  return false;
3057  }
3058  }
3059 
3060  case TM_Invisible:
3061  case TM_WouldBlock:
3062  case TM_BeingModified:
3063  /* these should not occur */
3064  elog(ERROR, "unexpected tuple operation result: %d", result);
3065  break;
3066  }
3067 
3068  /*
3069  * We've activated one of the WHEN clauses, so we don't search
3070  * further. This is required behaviour, not an optimization.
3071  */
3072  break;
3073  }
3074 
3075  /*
3076  * Successfully executed an action or no qualifying action was found.
3077  */
3078  return true;
3079 }
LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo)
Definition: execMain.c:2338
static bool ExecQual(ExprState *state, ExprContext *econtext)
Definition: executor.h:411
static Datum ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
Definition: executor.h:190
static bool ItemPointerIndicatesMovedPartitions(const ItemPointerData *pointer)
Definition: itemptr.h:197
static void ItemPointerCopy(const ItemPointerData *fromPointer, ItemPointerData *toPointer)
Definition: itemptr.h:172
@ LockWaitBlock
Definition: lockoptions.h:39
LockTupleMode
Definition: lockoptions.h:50
static void ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot)
static TM_Result ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, bool lockUpdated, UpdateContext *updateCxt)
static bool ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, TM_Result *result)
@ WCO_RLS_MERGE_UPDATE_CHECK
Definition: parsenodes.h:1318
@ WCO_RLS_MERGE_DELETE_CHECK
Definition: parsenodes.h:1319
TupleTableSlot * ecxt_innertuple
Definition: execnodes.h:251
List * ri_matchedMergeAction
Definition: execnodes.h:542
TransactionId xmax
Definition: tableam.h:143
ItemPointerData ctid
Definition: tableam.h:142
@ TM_BeingModified
Definition: tableam.h:99
@ TM_WouldBlock
Definition: tableam.h:102
@ TM_Invisible
Definition: tableam.h:80
static TM_Result table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd)
Definition: tableam.h:1585
#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION
Definition: tableam.h:260

References TM_FailureData::cmax, CMD_DELETE, CMD_NOTHING, CMD_UPDATE, MergeAction::commandType, TM_FailureData::ctid, ExprContext::ecxt_innertuple, ExprContext::ecxt_outertuple, ExprContext::ecxt_scantuple, elog(), ereport, errcode(), ERRCODE_T_R_SERIALIZATION_FAILURE, errhint(), errmsg(), ERROR, EState::es_output_cid, EState::es_processed, EState::es_snapshot, ModifyTableContext::estate, EvalPlanQual(), EvalPlanQualSlot(), ExecDeleteAct(), ExecDeleteEpilogue(), ExecDeletePrologue(), ExecGetJunkAttribute(), ExecProject(), ExecQual(), ExecUpdateAct(), ExecUpdateEpilogue(), ExecUpdateLockMode(), ExecUpdatePrologue(), ExecWithCheckOptions(), IsolationUsesXactSnapshot, ItemPointerCopy(), ItemPointerIndicatesMovedPartitions(), lfirst, LockWaitBlock, MergeActionState::mas_action, MergeActionState::mas_proj, MergeActionState::mas_whenqual, ModifyTableState::mt_epqstate, ModifyTableState::mt_merge_deleted, ModifyTableState::mt_merge_updated, ModifyTableContext::mtstate, NIL, ModifyTableContext::planSlot, ModifyTableState::ps, PlanState::ps_ExprContext, ModifyTableContext::relaction, ResultRelInfo::ri_matchedMergeAction, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_RangeTableIndex, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_RowIdAttNo, ResultRelInfo::ri_WithCheckOptions, SnapshotAny, PlanState::state, table_tuple_fetch_row_version(), table_tuple_lock(), TM_BeingModified, TM_Deleted, TM_Invisible, TM_Ok, TM_SelfModified, TM_Updated, TM_WouldBlock, ModifyTableContext::tmfd, TransactionIdIsCurrentTransactionId(), TupIsNull, TUPLE_LOCK_FLAG_FIND_LAST_VERSION, UpdateContext::updated, WCO_RLS_MERGE_DELETE_CHECK, WCO_RLS_MERGE_UPDATE_CHECK, and TM_FailureData::xmax.

Referenced by ExecMerge().

◆ ExecMergeNotMatched()

static void ExecMergeNotMatched ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
bool  canSetTag 
)
static

Definition at line 3085 of file nodeModifyTable.c.

3087 {
3088  ModifyTableState *mtstate = context->mtstate;
3089  ExprContext *econtext = mtstate->ps.ps_ExprContext;
3090  List *actionStates = NIL;
3091  ListCell *l;
3092 
3093  /*
3094  * For INSERT actions, the root relation's merge action is OK since the
3095  * INSERT's targetlist and the WHEN conditions can only refer to the
3096  * source relation and hence it does not matter which result relation we
3097  * work with.
3098  *
3099  * XXX does this mean that we can avoid creating copies of actionStates on
3100  * partitioned tables, for not-matched actions?
3101  */
3102  actionStates = resultRelInfo->ri_notMatchedMergeAction;
3103 
3104  /*
3105  * Make source tuple available to ExecQual and ExecProject. We don't need
3106  * the target tuple, since the WHEN quals and targetlist can't refer to
3107  * the target columns.
3108  */
3109  econtext->ecxt_scantuple = NULL;
3110  econtext->ecxt_innertuple = context->planSlot;
3111  econtext->ecxt_outertuple = NULL;
3112 
3113  foreach(l, actionStates)
3114  {
3116  CmdType commandType = action->mas_action->commandType;
3117  TupleTableSlot *newslot;
3118 
3119  /*
3120  * Test condition, if any.
3121  *
3122  * In the absence of any condition, we perform the action
3123  * unconditionally (no need to check separately since ExecQual() will
3124  * return true if there are no conditions to evaluate).
3125  */
3126  if (!ExecQual(action->mas_whenqual, econtext))
3127  continue;
3128 
3129  /* Perform stated action */
3130  switch (commandType)
3131  {
3132  case CMD_INSERT:
3133 
3134  /*
3135  * Project the tuple. In case of a partitioned table, the
3136  * projection was already built to use the root's descriptor,
3137  * so we don't need to map the tuple here.
3138  */
3139  newslot = ExecProject(action->mas_proj);
3140  context->relaction = action;
3141 
3142  (void) ExecInsert(context, mtstate->rootResultRelInfo, newslot,
3143  canSetTag, NULL, NULL);
3144  mtstate->mt_merge_inserted += 1;
3145  break;
3146  case CMD_NOTHING:
3147  /* Do nothing */
3148  break;
3149  default:
3150  elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
3151  }
3152 
3153  /*
3154  * We've activated one of the WHEN clauses, so we don't search
3155  * further. This is required behaviour, not an optimization.
3156  */
3157  break;
3158  }
3159 }
List * ri_notMatchedMergeAction
Definition: execnodes.h:543

References generate_unaccent_rules::action, CMD_INSERT, CMD_NOTHING, ExprContext::ecxt_innertuple, ExprContext::ecxt_outertuple, ExprContext::ecxt_scantuple, elog(), ERROR, ExecInsert(), ExecProject(), ExecQual(), lfirst, ModifyTableState::mt_merge_inserted, ModifyTableContext::mtstate, NIL, ModifyTableContext::planSlot, ModifyTableState::ps, PlanState::ps_ExprContext, ModifyTableContext::relaction, ResultRelInfo::ri_notMatchedMergeAction, and ModifyTableState::rootResultRelInfo.

Referenced by ExecMerge().

◆ ExecModifyTable()

static TupleTableSlot* ExecModifyTable ( PlanState pstate)
static

Definition at line 3510 of file nodeModifyTable.c.

3511 {
3512  ModifyTableState *node = castNode(ModifyTableState, pstate);
3513  ModifyTableContext context;
3514  EState *estate = node->ps.state;
3515  CmdType operation = node->operation;
3516  ResultRelInfo *resultRelInfo;
3517  PlanState *subplanstate;
3518  TupleTableSlot *slot;
3519  TupleTableSlot *oldSlot;
3520  ItemPointerData tuple_ctid;
3521  HeapTupleData oldtupdata;
3522  HeapTuple oldtuple;
3523  ItemPointer tupleid;
3524 
3526 
3527  /*
3528  * This should NOT get called during EvalPlanQual; we should have passed a
3529  * subplan tree to EvalPlanQual, instead. Use a runtime test not just
3530  * Assert because this condition is easy to miss in testing. (Note:
3531  * although ModifyTable should not get executed within an EvalPlanQual
3532  * operation, we do have to allow it to be initialized and shut down in
3533  * case it is within a CTE subplan. Hence this test must be here, not in
3534  * ExecInitModifyTable.)
3535  */
3536  if (estate->es_epq_active != NULL)
3537  elog(ERROR, "ModifyTable should not be called during EvalPlanQual");
3538 
3539  /*
3540  * If we've already completed processing, don't try to do more. We need
3541  * this test because ExecPostprocessPlan might call us an extra time, and
3542  * our subplan's nodes aren't necessarily robust against being called
3543  * extra times.
3544  */
3545  if (node->mt_done)
3546  return NULL;
3547 
3548  /*
3549  * On first call, fire BEFORE STATEMENT triggers before proceeding.
3550  */
3551  if (node->fireBSTriggers)
3552  {
3553  fireBSTriggers(node);
3554  node->fireBSTriggers = false;
3555  }
3556 
3557  /* Preload local variables */
3558  resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
3559  subplanstate = outerPlanState(node);
3560 
3561  /* Set global context */
3562  context.mtstate = node;
3563  context.epqstate = &node->mt_epqstate;
3564  context.estate = estate;
3565 
3566  /*
3567  * Fetch rows from subplan, and execute the required table modification
3568  * for each row.
3569  */
3570  for (;;)
3571  {
3572  /*
3573  * Reset the per-output-tuple exprcontext. This is needed because
3574  * triggers expect to use that context as workspace. It's a bit ugly
3575  * to do this below the top level of the plan, however. We might need
3576  * to rethink this later.
3577  */
3578  ResetPerTupleExprContext(estate);
3579 
3580  /*
3581  * Reset per-tuple memory context used for processing on conflict and
3582  * returning clauses, to free any expression evaluation storage
3583  * allocated in the previous cycle.
3584  */
3585  if (pstate->ps_ExprContext)
3587 
3588  context.planSlot = ExecProcNode(subplanstate);
3589 
3590  /* No more tuples to process? */
3591  if (TupIsNull(context.planSlot))
3592  break;
3593 
3594  /*
3595  * When there are multiple result relations, each tuple contains a
3596  * junk column that gives the OID of the rel from which it came.
3597  * Extract it and select the correct result relation.
3598  */
3600  {
3601  Datum datum;
3602  bool isNull;
3603  Oid resultoid;
3604 
3605  datum = ExecGetJunkAttribute(context.planSlot, node->mt_resultOidAttno,
3606  &isNull);
3607  if (isNull)
3608  {
3609  /*
3610  * For commands other than MERGE, any tuples having InvalidOid
3611  * for tableoid are errors. For MERGE, we may need to handle
3612  * them as WHEN NOT MATCHED clauses if any, so do that.
3613  *
3614  * Note that we use the node's toplevel resultRelInfo, not any
3615  * specific partition's.
3616  */
3617  if (operation == CMD_MERGE)
3618  {
3619  EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
3620 
3621  ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
3622  continue; /* no RETURNING support yet */
3623  }
3624 
3625  elog(ERROR, "tableoid is NULL");
3626  }
3627  resultoid = DatumGetObjectId(datum);
3628 
3629  /* If it's not the same as last time, we need to locate the rel */
3630  if (resultoid != node->mt_lastResultOid)
3631  resultRelInfo = ExecLookupResultRelByOid(node, resultoid,
3632  false, true);
3633  }
3634 
3635  /*
3636  * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
3637  * here is compute the RETURNING expressions.
3638  */
3639  if (resultRelInfo->ri_usesFdwDirectModify)
3640  {
3641  Assert(resultRelInfo->ri_projectReturning);
3642 
3643  /*
3644  * A scan slot containing the data that was actually inserted,
3645  * updated or deleted has already been made available to
3646  * ExecProcessReturning by IterateDirectModify, so no need to
3647  * provide it here.
3648  */
3649  slot = ExecProcessReturning(resultRelInfo, NULL, context.planSlot);
3650 
3651  return slot;
3652  }
3653 
3654  EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
3655  slot = context.planSlot;
3656 
3657  tupleid = NULL;
3658  oldtuple = NULL;
3659 
3660  /*
3661  * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
3662  * to be updated/deleted/merged. For a heap relation, that's a TID;
3663  * otherwise we may have a wholerow junk attr that carries the old
3664  * tuple in toto. Keep this in step with the part of
3665  * ExecInitModifyTable that sets up ri_RowIdAttNo.
3666  */
3667  if (operation == CMD_UPDATE || operation == CMD_DELETE ||
3668  operation == CMD_MERGE)
3669  {
3670  char relkind;
3671  Datum datum;
3672  bool isNull;
3673 
3674  relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
3675  if (relkind == RELKIND_RELATION ||
3676  relkind == RELKIND_MATVIEW ||
3677  relkind == RELKIND_PARTITIONED_TABLE)
3678  {
3679  /* ri_RowIdAttNo refers to a ctid attribute */
3680  Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));
3681  datum = ExecGetJunkAttribute(slot,
3682  resultRelInfo->ri_RowIdAttNo,
3683  &isNull);
3684 
3685  /*
3686  * For commands other than MERGE, any tuples having a null row
3687  * identifier are errors. For MERGE, we may need to handle
3688  * them as WHEN NOT MATCHED clauses if any, so do that.
3689  *
3690  * Note that we use the node's toplevel resultRelInfo, not any
3691  * specific partition's.
3692  */
3693  if (isNull)
3694  {
3695  if (operation == CMD_MERGE)
3696  {
3697  EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
3698 
3699  ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
3700  continue; /* no RETURNING support yet */
3701  }
3702 
3703  elog(ERROR, "ctid is NULL");
3704  }
3705 
3706  tupleid = (ItemPointer) DatumGetPointer(datum);
3707  tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
3708  tupleid = &tuple_ctid;
3709  }
3710 
3711  /*
3712  * Use the wholerow attribute, when available, to reconstruct the
3713  * old relation tuple. The old tuple serves one or both of two
3714  * purposes: 1) it serves as the OLD tuple for row triggers, 2) it
3715  * provides values for any unchanged columns for the NEW tuple of
3716  * an UPDATE, because the subplan does not produce all the columns
3717  * of the target table.
3718  *
3719  * Note that the wholerow attribute does not carry system columns,
3720  * so foreign table triggers miss seeing those, except that we
3721  * know enough here to set t_tableOid. Quite separately from
3722  * this, the FDW may fetch its own junk attrs to identify the row.
3723  *
3724  * Other relevant relkinds, currently limited to views, always
3725  * have a wholerow attribute.
3726  */
3727  else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
3728  {
3729  datum = ExecGetJunkAttribute(slot,
3730  resultRelInfo->ri_RowIdAttNo,
3731  &isNull);
3732  /* shouldn't ever get a null result... */
3733  if (isNull)
3734  elog(ERROR, "wholerow is NULL");
3735 
3736  oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
3737  oldtupdata.t_len =
3739  ItemPointerSetInvalid(&(oldtupdata.t_self));
3740  /* Historically, view triggers see invalid t_tableOid. */
3741  oldtupdata.t_tableOid =
3742  (relkind == RELKIND_VIEW) ? InvalidOid :
3743  RelationGetRelid(resultRelInfo->ri_RelationDesc);
3744 
3745  oldtuple = &oldtupdata;
3746  }
3747  else
3748  {
3749  /* Only foreign tables are allowed to omit a row-ID attr */
3750  Assert(relkind == RELKIND_FOREIGN_TABLE);
3751  }
3752  }
3753 
3754  switch (operation)
3755  {
3756  case CMD_INSERT:
3757  /* Initialize projection info if first time for this table */
3758  if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
3759  ExecInitInsertProjection(node, resultRelInfo);
3760  slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot);
3761  slot = ExecInsert(&context, resultRelInfo, slot,
3762  node->canSetTag, NULL, NULL);
3763  break;
3764 
3765  case CMD_UPDATE:
3766  /* Initialize projection info if first time for this table */
3767  if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
3768  ExecInitUpdateProjection(node, resultRelInfo);
3769 
3770  /*
3771  * Make the new tuple by combining plan's output tuple with
3772  * the old tuple being updated.
3773  */
3774  oldSlot = resultRelInfo->ri_oldTupleSlot;
3775  if (oldtuple != NULL)
3776  {
3777  /* Use the wholerow junk attr as the old tuple. */
3778  ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
3779  }
3780  else
3781  {
3782  /* Fetch the most recent version of old tuple. */
3783  Relation relation = resultRelInfo->ri_RelationDesc;
3784 
3785  if (!table_tuple_fetch_row_version(relation, tupleid,
3786  SnapshotAny,
3787  oldSlot))
3788  elog(ERROR, "failed to fetch tuple being updated");
3789  }
3790  slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot,
3791  oldSlot);
3792  context.relaction = NULL;
3793 
3794  /* Now apply the update. */
3795  slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
3796  slot, node->canSetTag, false);
3797  break;
3798 
3799  case CMD_DELETE:
3800  slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple,
3801  true, false, node->canSetTag, NULL, NULL);
3802  break;
3803 
3804  case CMD_MERGE:
3805  slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
3806  break;
3807 
3808  default:
3809  elog(ERROR, "unknown operation");
3810  break;
3811  }
3812 
3813  /*
3814  * If we got a RETURNING result, return it to caller. We'll continue
3815  * the work on next call.
3816  */
3817  if (slot)
3818  return slot;
3819  }
3820 
3821  /*
3822  * Insert remaining tuples for batch insert.
3823  */
3824  if (estate->es_insert_pending_result_relations != NIL)
3825  ExecPendingInserts(estate);
3826 
3827  /*
3828  * We're done, but fire AFTER STATEMENT triggers before exiting.
3829  */
3830  fireASTriggers(node);
3831 
3832  node->mt_done = true;
3833 
3834  return NULL;
3835 }
#define ResetPerTupleExprContext(estate)
Definition: executor.h:557
#define ResetExprContext(econtext)
Definition: executor.h:542
#define EvalPlanQualSetSlot(epqstate, slot)
Definition: executor.h:242
static TupleTableSlot * ExecProcNode(PlanState *node)
Definition: executor.h:267
#define DatumGetHeapTupleHeader(X)
Definition: fmgr.h:295
#define HeapTupleHeaderGetDatumLength(tup)
Definition: htup_details.h:450
static void ItemPointerSetInvalid(ItemPointerData *pointer)
Definition: itemptr.h:184
ItemPointerData * ItemPointer
Definition: itemptr.h:49
static void ExecInitInsertProjection(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo)
static TupleTableSlot * ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, bool canSetTag)
static TupleTableSlot * ExecGetInsertNewTuple(ResultRelInfo *relinfo, TupleTableSlot *planSlot)
ResultRelInfo * ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid, bool missing_ok, bool update_cache)
static void fireBSTriggers(ModifyTableState *node)
static TupleTableSlot * ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, bool locked)
static void fireASTriggers(ModifyTableState *node)
#define castNode(_type_, nodeptr)
Definition: nodes.h:197
static Oid DatumGetObjectId(Datum X)
Definition: postgres.h:242
static Pointer DatumGetPointer(Datum X)
Definition: postgres.h:312
struct EPQState * es_epq_active
Definition: execnodes.h:689
ItemPointerData t_self
Definition: htup.h:65
uint32 t_len
Definition: htup.h:64
HeapTupleHeader t_data
Definition: htup.h:68
Oid t_tableOid
Definition: htup.h:66

References Assert(), AttributeNumberIsValid, ModifyTableState::canSetTag, castNode, CHECK_FOR_INTERRUPTS, CMD_DELETE, CMD_INSERT, CMD_MERGE, CMD_UPDATE, DatumGetHeapTupleHeader, DatumGetObjectId(), DatumGetPointer(), elog(), ModifyTableContext::epqstate, ERROR, EState::es_epq_active, EState::es_insert_pending_result_relations, ModifyTableContext::estate, EvalPlanQualSetSlot, ExecDelete(), ExecForceStoreHeapTuple(), ExecGetInsertNewTuple(), ExecGetJunkAttribute(), ExecGetUpdateNewTuple(), ExecInitInsertProjection(), ExecInitUpdateProjection(), ExecInsert(), ExecLookupResultRelByOid(), ExecMerge(), ExecPendingInserts(), ExecProcessReturning(), ExecProcNode(), ExecUpdate(), fireASTriggers(), fireBSTriggers(), ModifyTableState::fireBSTriggers, HeapTupleHeaderGetDatumLength, InvalidOid, ItemPointerSetInvalid(), ModifyTableState::mt_done, ModifyTableState::mt_epqstate, ModifyTableState::mt_lastResultIndex, ModifyTableState::mt_lastResultOid, ModifyTableState::mt_resultOidAttno, ModifyTableContext::mtstate, NIL, ModifyTableState::operation, outerPlanState, ModifyTableContext::planSlot, ModifyTableState::ps, PlanState::ps_ExprContext, RelationData::rd_rel, ModifyTableContext::relaction, RelationGetRelid, ResetExprContext, ResetPerTupleExprContext, ModifyTableState::resultRelInfo, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_RowIdAttNo, ResultRelInfo::ri_usesFdwDirectModify, SnapshotAny, PlanState::state, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, HeapTupleData::t_tableOid, table_tuple_fetch_row_version(), TupIsNull, and unlikely.

Referenced by ExecInitModifyTable().

◆ ExecOnConflictUpdate()

static bool ExecOnConflictUpdate ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  conflictTid,
TupleTableSlot excludedSlot,
bool  canSetTag,
TupleTableSlot **  returning 
)
static

Definition at line 2471 of file nodeModifyTable.c.

2477 {
2478  ModifyTableState *mtstate = context->mtstate;
2479  ExprContext *econtext = mtstate->ps.ps_ExprContext;
2480  Relation relation = resultRelInfo->ri_RelationDesc;
2481  ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
2482  TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
2483  TM_FailureData tmfd;
2484  LockTupleMode lockmode;
2485  TM_Result test;
2486  Datum xminDatum;
2487  TransactionId xmin;
2488  bool isnull;
2489 
2490  /* Determine lock mode to use */
2491  lockmode = ExecUpdateLockMode(context->estate, resultRelInfo);
2492 
2493  /*
2494  * Lock tuple for update. Don't follow updates when tuple cannot be
2495  * locked without doing so. A row locking conflict here means our
2496  * previous conclusion that the tuple is conclusively committed is not
2497  * true anymore.
2498  */
2499  test = table_tuple_lock(relation, conflictTid,
2500  context->estate->es_snapshot,
2501  existing, context->estate->es_output_cid,
2502  lockmode, LockWaitBlock, 0,
2503  &tmfd);
2504  switch (test)
2505  {
2506  case TM_Ok:
2507  /* success! */
2508  break;
2509 
2510  case TM_Invisible:
2511 
2512  /*
2513  * This can occur when a just inserted tuple is updated again in
2514  * the same command. E.g. because multiple rows with the same
2515  * conflicting key values are inserted.
2516  *
2517  * This is somewhat similar to the ExecUpdate() TM_SelfModified
2518  * case. We do not want to proceed because it would lead to the
2519  * same row being updated a second time in some unspecified order,
2520  * and in contrast to plain UPDATEs there's no historical behavior
2521  * to break.
2522  *
2523  * It is the user's responsibility to prevent this situation from
2524  * occurring. These problems are why the SQL standard similarly
2525  * specifies that for SQL MERGE, an exception must be raised in
2526  * the event of an attempt to update the same row twice.
2527  */
2528  xminDatum = slot_getsysattr(existing,
2530  &isnull);
2531  Assert(!isnull);
2532  xmin = DatumGetTransactionId(xminDatum);
2533 
2535  ereport(ERROR,
2536  (errcode(ERRCODE_CARDINALITY_VIOLATION),
2537  /* translator: %s is a SQL command name */
2538  errmsg("%s command cannot affect row a second time",
2539  "ON CONFLICT DO UPDATE"),
2540  errhint("Ensure that no rows proposed for insertion within the same command have duplicate constrained values.")));
2541 
2542  /* This shouldn't happen */
2543  elog(ERROR, "attempted to lock invisible tuple");
2544  break;
2545 
2546  case TM_SelfModified:
2547 
2548  /*
2549  * This state should never be reached. As a dirty snapshot is used
2550  * to find conflicting tuples, speculative insertion wouldn't have
2551  * seen this row to conflict with.
2552  */
2553  elog(ERROR, "unexpected self-updated tuple");
2554  break;
2555 
2556  case TM_Updated:
2558  ereport(ERROR,
2560  errmsg("could not serialize access due to concurrent update")));
2561 
2562  /*
2563  * As long as we don't support an UPDATE of INSERT ON CONFLICT for
2564  * a partitioned table we shouldn't reach to a case where tuple to
2565  * be lock is moved to another partition due to concurrent update
2566  * of the partition key.
2567  */
2569 
2570  /*
2571  * Tell caller to try again from the very start.
2572  *
2573  * It does not make sense to use the usual EvalPlanQual() style
2574  * loop here, as the new version of the row might not conflict
2575  * anymore, or the conflicting tuple has actually been deleted.
2576  */
2577  ExecClearTuple(existing);
2578  return false;
2579 
2580  case TM_Deleted:
2582  ereport(ERROR,
2584  errmsg("could not serialize access due to concurrent delete")));
2585 
2586  /* see TM_Updated case */
2588  ExecClearTuple(existing);
2589  return false;
2590 
2591  default:
2592  elog(ERROR, "unrecognized table_tuple_lock status: %u", test);
2593  }
2594 
2595  /* Success, the tuple is locked. */
2596 
2597  /*
2598  * Verify that the tuple is visible to our MVCC snapshot if the current
2599  * isolation level mandates that.
2600  *
2601  * It's not sufficient to rely on the check within ExecUpdate() as e.g.
2602  * CONFLICT ... WHERE clause may prevent us from reaching that.
2603  *
2604  * This means we only ever continue when a new command in the current
2605  * transaction could see the row, even though in READ COMMITTED mode the
2606  * tuple will not be visible according to the current statement's
2607  * snapshot. This is in line with the way UPDATE deals with newer tuple
2608  * versions.
2609  */
2610  ExecCheckTupleVisible(context->estate, relation, existing);
2611 
2612  /*
2613  * Make tuple and any needed join variables available to ExecQual and
2614  * ExecProject. The EXCLUDED tuple is installed in ecxt_innertuple, while
2615  * the target's existing tuple is installed in the scantuple. EXCLUDED
2616  * has been made to reference INNER_VAR in setrefs.c, but there is no
2617  * other redirection.
2618  */
2619  econtext->ecxt_scantuple = existing;
2620  econtext->ecxt_innertuple = excludedSlot;
2621  econtext->ecxt_outertuple = NULL;
2622 
2623  if (!ExecQual(onConflictSetWhere, econtext))
2624  {
2625  ExecClearTuple(existing); /* see return below */
2626  InstrCountFiltered1(&mtstate->ps, 1);
2627  return true; /* done with the tuple */
2628  }
2629 
2630  if (resultRelInfo->ri_WithCheckOptions != NIL)
2631  {
2632  /*
2633  * Check target's existing tuple against UPDATE-applicable USING
2634  * security barrier quals (if any), enforced here as RLS checks/WCOs.
2635  *
2636  * The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
2637  * quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK,
2638  * but that's almost the extent of its special handling for ON
2639  * CONFLICT DO UPDATE.
2640  *
2641  * The rewriter will also have associated UPDATE applicable straight
2642  * RLS checks/WCOs for the benefit of the ExecUpdate() call that
2643  * follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
2644  * kinds, so there is no danger of spurious over-enforcement in the
2645  * INSERT or UPDATE path.
2646  */
2648  existing,
2649  mtstate->ps.state);
2650  }
2651 
2652  /* Project the new tuple version */
2653  ExecProject(resultRelInfo->ri_onConflict->oc_ProjInfo);
2654 
2655  /*
2656  * Note that it is possible that the target tuple has been modified in
2657  * this session, after the above table_tuple_lock. We choose to not error
2658  * out in that case, in line with ExecUpdate's treatment of similar cases.
2659  * This can happen if an UPDATE is triggered from within ExecQual(),
2660  * ExecWithCheckOptions() or ExecProject() above, e.g. by selecting from a
2661  * wCTE in the ON CONFLICT's SET.
2662  */
2663 
2664  /* Execute UPDATE with projection */
2665  *returning = ExecUpdate(context, resultRelInfo,
2666  conflictTid, NULL,
2667  resultRelInfo->ri_onConflict->oc_ProjSlot,
2668  canSetTag, true);
2669 
2670  /*
2671  * Clear out existing tuple, as there might not be another conflict among
2672  * the next input rows. Don't want to hold resources till the end of the
2673  * query.
2674  */
2675  ExecClearTuple(existing);
2676  return true;
2677 }
#define InstrCountFiltered1(node, delta)
Definition: execnodes.h:1139
@ WCO_RLS_CONFLICT_CHECK
Definition: parsenodes.h:1317
static void test(void)

References Assert(), TM_FailureData::ctid, DatumGetTransactionId(), ExprContext::ecxt_innertuple, ExprContext::ecxt_outertuple, ExprContext::ecxt_scantuple, elog(), ereport, errcode(), ERRCODE_T_R_SERIALIZATION_FAILURE, errhint(), errmsg(), ERROR, EState::es_output_cid, EState::es_snapshot, ModifyTableContext::estate, ExecCheckTupleVisible(), ExecClearTuple(), ExecProject(), ExecQual(), ExecUpdate(), ExecUpdateLockMode(), ExecWithCheckOptions(), InstrCountFiltered1, IsolationUsesXactSnapshot, ItemPointerIndicatesMovedPartitions(), LockWaitBlock, MinTransactionIdAttributeNumber, ModifyTableContext::mtstate, NIL, OnConflictSetState::oc_Existing, OnConflictSetState::oc_ProjInfo, OnConflictSetState::oc_ProjSlot, OnConflictSetState::oc_WhereClause, ModifyTableState::ps, PlanState::ps_ExprContext, ResultRelInfo::ri_onConflict, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_WithCheckOptions, slot_getsysattr(), PlanState::state, table_tuple_lock(), test(), TM_Deleted, TM_Invisible, TM_Ok, TM_SelfModified, TM_Updated, TransactionIdIsCurrentTransactionId(), and WCO_RLS_CONFLICT_CHECK.

Referenced by ExecInsert().

◆ ExecPendingInserts()

static void ExecPendingInserts ( EState estate)
static

Definition at line 1270 of file nodeModifyTable.c.

1271 {
1272  ListCell *l1,
1273  *l2;
1274 
1276  l2, estate->es_insert_pending_modifytables)
1277  {
1278  ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l1);
1279  ModifyTableState *mtstate = (ModifyTableState *) lfirst(l2);
1280 
1281  Assert(mtstate);
1282  ExecBatchInsert(mtstate, resultRelInfo,
1283  resultRelInfo->ri_Slots,
1284  resultRelInfo->ri_PlanSlots,
1285  resultRelInfo->ri_NumSlots,
1286  estate, mtstate->canSetTag);
1287  resultRelInfo->ri_NumSlots = 0;
1288  }
1289 
1294 }
#define forboth(cell1, list1, cell2, list2)
Definition: pg_list.h:467

References Assert(), ModifyTableState::canSetTag, EState::es_insert_pending_modifytables, EState::es_insert_pending_result_relations, ExecBatchInsert(), forboth, lfirst, list_free(), NIL, ResultRelInfo::ri_NumSlots, ResultRelInfo::ri_PlanSlots, and ResultRelInfo::ri_Slots.

Referenced by ExecDeletePrologue(), ExecInsert(), ExecModifyTable(), and ExecUpdatePrologue().

◆ ExecPrepareTupleRouting()

static TupleTableSlot * ExecPrepareTupleRouting ( ModifyTableState mtstate,
EState estate,
PartitionTupleRouting proute,
ResultRelInfo targetRelInfo,
TupleTableSlot slot,
ResultRelInfo **  partRelInfo 
)
static

Definition at line 3450 of file nodeModifyTable.c.

3456 {
3457  ResultRelInfo *partrel;
3458  TupleConversionMap *map;
3459 
3460  /*
3461  * Lookup the target partition's ResultRelInfo. If ExecFindPartition does
3462  * not find a valid partition for the tuple in 'slot' then an error is
3463  * raised. An error may also be raised if the found partition is not a
3464  * valid target for INSERTs. This is required since a partitioned table
3465  * UPDATE to another partition becomes a DELETE+INSERT.
3466  */
3467  partrel = ExecFindPartition(mtstate, targetRelInfo, proute, slot, estate);
3468 
3469  /*
3470  * If we're capturing transition tuples, we might need to convert from the
3471  * partition rowtype to root partitioned table's rowtype. But if there
3472  * are no BEFORE triggers on the partition that could change the tuple, we
3473  * can just remember the original unconverted tuple to avoid a needless
3474  * round trip conversion.
3475  */
3476  if (mtstate->mt_transition_capture != NULL)
3477  {
3478  bool has_before_insert_row_trig;
3479 
3480  has_before_insert_row_trig = (partrel->ri_TrigDesc &&
3482 
3484  !has_before_insert_row_trig ? slot : NULL;
3485  }
3486 
3487  /*
3488  * Convert the tuple, if necessary.
3489  */
3490  map = ExecGetRootToChildMap(partrel, estate);
3491  if (map != NULL)
3492  {
3493  TupleTableSlot *new_slot = partrel->ri_PartitionTupleSlot;
3494 
3495  slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
3496  }
3497 
3498  *partRelInfo = partrel;
3499  return slot;
3500 }
ResultRelInfo * ExecFindPartition(ModifyTableState *mtstate, ResultRelInfo *rootResultRelInfo, PartitionTupleRouting *proute, TupleTableSlot *slot, EState *estate)
TupleConversionMap * ExecGetRootToChildMap(ResultRelInfo *resultRelInfo, EState *estate)
Definition: execUtils.c:1263
TupleTableSlot * ri_PartitionTupleSlot
Definition: execnodes.h:575

References TupleConversionMap::attrMap, ExecFindPartition(), ExecGetRootToChildMap(), execute_attr_map_slot(), ModifyTableState::mt_transition_capture, ResultRelInfo::ri_PartitionTupleSlot, ResultRelInfo::ri_TrigDesc, TransitionCaptureState::tcs_original_insert_tuple, and TriggerDesc::trig_insert_before_row.

Referenced by ExecInsert().

◆ ExecProcessReturning()

static TupleTableSlot* ExecProcessReturning ( ResultRelInfo resultRelInfo,
TupleTableSlot tupleSlot,
TupleTableSlot planSlot 
)
static

Definition at line 250 of file nodeModifyTable.c.

253 {
254  ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
255  ExprContext *econtext = projectReturning->pi_exprContext;
256 
257  /* Make tuple and any needed join variables available to ExecProject */
258  if (tupleSlot)
259  econtext->ecxt_scantuple = tupleSlot;
260  econtext->ecxt_outertuple = planSlot;
261 
262  /*
263  * RETURNING expressions might reference the tableoid column, so
264  * reinitialize tts_tableOid before evaluating them.
265  */
266  econtext->ecxt_scantuple->tts_tableOid =
267  RelationGetRelid(resultRelInfo->ri_RelationDesc);
268 
269  /* Compute the RETURNING expressions */
270  return ExecProject(projectReturning);
271 }

References ExprContext::ecxt_outertuple, ExprContext::ecxt_scantuple, ExecProject(), ProjectionInfo::pi_exprContext, RelationGetRelid, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RelationDesc, and TupleTableSlot::tts_tableOid.

Referenced by ExecDelete(), ExecInsert(), ExecModifyTable(), and ExecUpdate().

◆ ExecReScanModifyTable()

void ExecReScanModifyTable ( ModifyTableState node)

Definition at line 4442 of file nodeModifyTable.c.

4443 {
4444  /*
4445  * Currently, we don't need to support rescan on ModifyTable nodes. The
4446  * semantics of that would be a bit debatable anyway.
4447  */
4448  elog(ERROR, "ExecReScanModifyTable is not implemented");
4449 }

References elog(), and ERROR.

Referenced by ExecReScan().

◆ ExecSetupTransitionCaptureState()

static void ExecSetupTransitionCaptureState ( ModifyTableState mtstate,
EState estate 
)
static

Definition at line 3421 of file nodeModifyTable.c.

3422 {
3423  ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
3424  ResultRelInfo *targetRelInfo = mtstate->rootResultRelInfo;
3425 
3426  /* Check for transition tables on the directly targeted relation. */
3427  mtstate->mt_transition_capture =
3428  MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
3429  RelationGetRelid(targetRelInfo->ri_RelationDesc),
3430  mtstate->operation);
3431  if (plan->operation == CMD_INSERT &&
3433  mtstate->mt_oc_transition_capture =
3434  MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
3435  RelationGetRelid(targetRelInfo->ri_RelationDesc),
3436  CMD_UPDATE);
3437 }
struct TransitionCaptureState * mt_oc_transition_capture
Definition: execnodes.h:1304
TransitionCaptureState * MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
Definition: trigger.c:4844

References CMD_INSERT, CMD_UPDATE, MakeTransitionCaptureState(), ModifyTableState::mt_oc_transition_capture, ModifyTableState::mt_transition_capture, ONCONFLICT_UPDATE, ModifyTable::onConflictAction, ModifyTableState::operation, ModifyTable::operation, PlanState::plan, ModifyTableState::ps, RelationGetRelid, and ModifyTableState::rootResultRelInfo.

Referenced by ExecInitModifyTable().

◆ ExecUpdate()

static TupleTableSlot* ExecUpdate ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot slot,
bool  canSetTag,
bool  locked 
)
static

Definition at line 2270 of file nodeModifyTable.c.

2273 {
2274  EState *estate = context->estate;
2275  Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
2276  UpdateContext updateCxt = {0};
2277  TM_Result result;
2278 
2279  /*
2280  * abort the operation if not running transactions
2281  */
2283  elog(ERROR, "cannot UPDATE during bootstrap");
2284 
2285  /*
2286  * Prepare for the update. This includes BEFORE ROW triggers, so we're
2287  * done if it says we are.
2288  */
2289  if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL))
2290  return NULL;
2291 
2292  /* INSTEAD OF ROW UPDATE Triggers */
2293  if (resultRelInfo->ri_TrigDesc &&
2294  resultRelInfo->ri_TrigDesc->trig_update_instead_row)
2295  {
2296  if (!ExecIRUpdateTriggers(estate, resultRelInfo,
2297  oldtuple, slot))
2298  return NULL; /* "do nothing" */
2299  }
2300  else if (resultRelInfo->ri_FdwRoutine)
2301  {
2302  /* Fill in GENERATEd columns */
2303  ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
2304 
2305  /*
2306  * update in foreign table: let the FDW do it
2307  */
2308  slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
2309  resultRelInfo,
2310  slot,
2311  context->planSlot);
2312 
2313  if (slot == NULL) /* "do nothing" */
2314  return NULL;
2315 
2316  /*
2317  * AFTER ROW Triggers or RETURNING expressions might reference the
2318  * tableoid column, so (re-)initialize tts_tableOid before evaluating
2319  * them. (This covers the case where the FDW replaced the slot.)
2320  */
2321  slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
2322  }
2323  else
2324  {
2325  /*
2326  * If we generate a new candidate tuple after EvalPlanQual testing, we
2327  * must loop back here to try again. (We don't need to redo triggers,
2328  * however. If there are any BEFORE triggers then trigger.c will have
2329  * done table_tuple_lock to lock the correct tuple, so there's no need
2330  * to do them again.)
2331  */
2332 redo_act:
2333  result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
2334  canSetTag, !IsolationUsesXactSnapshot(),
2335  &updateCxt);
2336 
2337  /*
2338  * If ExecUpdateAct reports that a cross-partition update was done,
2339  * then the RETURNING tuple (if any) has been projected and there's
2340  * nothing else for us to do.
2341  */
2342  if (updateCxt.crossPartUpdate)
2343  return context->cpUpdateReturningSlot;
2344 
2345  switch (result)
2346  {
2347  case TM_SelfModified:
2348 
2349  /*
2350  * The target tuple was already updated or deleted by the
2351  * current command, or by a later command in the current
2352  * transaction. The former case is possible in a join UPDATE
2353  * where multiple tuples join to the same target tuple. This
2354  * is pretty questionable, but Postgres has always allowed it:
2355  * we just execute the first update action and ignore
2356  * additional update attempts.
2357  *
2358  * The latter case arises if the tuple is modified by a
2359  * command in a BEFORE trigger, or perhaps by a command in a
2360  * volatile function used in the query. In such situations we
2361  * should not ignore the update, but it is equally unsafe to
2362  * proceed. We don't want to discard the original UPDATE
2363  * while keeping the triggered actions based on it; and we
2364  * have no principled way to merge this update with the
2365  * previous ones. So throwing an error is the only safe
2366  * course.
2367  *
2368  * If a trigger actually intends this type of interaction, it
2369  * can re-execute the UPDATE (assuming it can figure out how)
2370  * and then return NULL to cancel the outer update.
2371  */
2372  if (context->tmfd.cmax != estate->es_output_cid)
2373  ereport(ERROR,
2374  (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
2375  errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
2376  errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
2377 
2378  /* Else, already updated by self; nothing to do */
2379  return NULL;
2380 
2381  case TM_Ok:
2382  break;
2383 
2384  case TM_Updated:
2385  {
2386  TupleTableSlot *inputslot;
2387  TupleTableSlot *epqslot;
2388  TupleTableSlot *oldSlot;
2389 
2391  ereport(ERROR,
2393  errmsg("could not serialize access due to concurrent update")));
2394  Assert(!locked);
2395 
2396  /*
2397  * ExecUpdateAct() has already locked the old tuple for
2398  * us. Now we need to copy it to the right slot.
2399  */
2400  inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
2401  resultRelInfo->ri_RangeTableIndex);
2402 
2403  /* Make sure ri_oldTupleSlot is initialized. */
2404  if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
2406  resultRelInfo);
2407 
2408  /*
2409  * Save the locked tuple for further calculation of the
2410  * new tuple.
2411  */
2412  oldSlot = resultRelInfo->ri_oldTupleSlot;
2413  ExecCopySlot(oldSlot, inputslot);
2414  ExecMaterializeSlot(oldSlot);
2415  Assert(context->tmfd.traversed);
2416 
2417  epqslot = EvalPlanQual(context->epqstate,
2418  resultRelationDesc,
2419  resultRelInfo->ri_RangeTableIndex,
2420  inputslot);
2421  if (TupIsNull(epqslot))
2422  /* Tuple not passing quals anymore, exiting... */
2423  return NULL;
2424  slot = ExecGetUpdateNewTuple(resultRelInfo,
2425  epqslot, oldSlot);
2426  goto redo_act;
2427  }
2428 
2429  break;
2430 
2431  case TM_Deleted:
2433  ereport(ERROR,
2435  errmsg("could not serialize access due to concurrent delete")));
2436  /* tuple already deleted; nothing to do */
2437  return NULL;
2438 
2439  default:
2440  elog(ERROR, "unrecognized table_tuple_update status: %u",
2441  result);
2442  return NULL;
2443  }
2444  }
2445 
2446  if (canSetTag)
2447  (estate->es_processed)++;
2448 
2449  ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
2450  slot);
2451 
2452  /* Process RETURNING if present */
2453  if (resultRelInfo->ri_projectReturning)
2454  return ExecProcessReturning(resultRelInfo, slot, context->planSlot);
2455 
2456  return NULL;
2457 }
#define IsBootstrapProcessingMode()
Definition: miscadmin.h:405
static void ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
ExecForeignUpdate_function ExecForeignUpdate
Definition: fdwapi.h:235
bool trig_update_instead_row
Definition: reltrigger.h:63
bool ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple, TupleTableSlot *newslot)
Definition: trigger.c:3149

References Assert(), TM_FailureData::cmax, ModifyTableContext::cpUpdateReturningSlot, UpdateContext::crossPartUpdate, elog(), ModifyTableContext::epqstate, ereport, errcode(), ERRCODE_T_R_SERIALIZATION_FAILURE, errhint(), errmsg(), ERROR, EState::es_output_cid, EState::es_processed, ModifyTableContext::estate, EvalPlanQual(), EvalPlanQualSlot(), ExecCopySlot(), FdwRoutine::ExecForeignUpdate, ExecGetUpdateNewTuple(), ExecInitUpdateProjection(), ExecIRUpdateTriggers(), ExecMaterializeSlot(), ExecProcessReturning(), ExecUpdateAct(), ExecUpdateEpilogue(), ExecUpdatePrepareSlot(), ExecUpdatePrologue(), IsBootstrapProcessingMode, IsolationUsesXactSnapshot, ModifyTableContext::mtstate, ModifyTableContext::planSlot, RelationGetRelid, ResultRelInfo::ri_FdwRoutine, ResultRelInfo::ri_oldTupleSlot, ResultRelInfo::ri_projectNewInfoValid, ResultRelInfo::ri_projectReturning, ResultRelInfo::ri_RangeTableIndex, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, TM_Deleted, TM_Ok, TM_SelfModified, TM_Updated, ModifyTableContext::tmfd, TM_FailureData::traversed, TriggerDesc::trig_update_instead_row, TupleTableSlot::tts_tableOid, TupIsNull, and unlikely.

Referenced by ExecModifyTable(), and ExecOnConflictUpdate().

◆ ExecUpdateAct()

static TM_Result ExecUpdateAct ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot slot,
bool  canSetTag,
bool  lockUpdated,
UpdateContext updateCxt 
)
static

Definition at line 1966 of file nodeModifyTable.c.

1969 {
1970  EState *estate = context->estate;
1971  Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
1972  bool partition_constraint_failed;
1973  TM_Result result;
1974  GetEPQSlotArg slotArg = {context->epqstate, resultRelInfo};
1975  LazyTupleTableSlot lazyEPQSlot,
1976  *lazyEPQSlotPtr;
1977 
1978  updateCxt->crossPartUpdate = false;
1979 
1980  /*
1981  * If we move the tuple to a new partition, we loop back here to recompute
1982  * GENERATED values (which are allowed to be different across partitions)
1983  * and recheck any RLS policies and constraints. We do not fire any
1984  * BEFORE triggers of the new partition, however.
1985  */
1986 lreplace:
1987  /* Fill in GENERATEd columns */
1988  ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
1989 
1990  /* ensure slot is independent, consider e.g. EPQ */
1991  ExecMaterializeSlot(slot);
1992 
1993  /*
1994  * If partition constraint fails, this row might get moved to another
1995  * partition, in which case we should check the RLS CHECK policy just
1996  * before inserting into the new partition, rather than doing it here.
1997  * This is because a trigger on that partition might again change the row.
1998  * So skip the WCO checks if the partition constraint fails.
1999  */
2000  partition_constraint_failed =
2001  resultRelationDesc->rd_rel->relispartition &&
2002  !ExecPartitionCheck(resultRelInfo, slot, estate, false);
2003 
2004  /* Check any RLS UPDATE WITH CHECK policies */
2005  if (!partition_constraint_failed &&
2006  resultRelInfo->ri_WithCheckOptions != NIL)
2007  {
2008  /*
2009  * ExecWithCheckOptions() will skip any WCOs which are not of the kind
2010  * we are looking for at this point.
2011  */
2013  resultRelInfo, slot, estate);
2014  }
2015 
2016  /*
2017  * If a partition check failed, try to move the row into the right
2018  * partition.
2019  */
2020  if (partition_constraint_failed)
2021  {
2022  TupleTableSlot *inserted_tuple,
2023  *retry_slot;
2024  ResultRelInfo *insert_destrel = NULL;
2025 
2026  /*
2027  * ExecCrossPartitionUpdate will first DELETE the row from the
2028  * partition it's currently in and then insert it back into the root
2029  * table, which will re-route it to the correct partition. However,
2030  * if the tuple has been concurrently updated, a retry is needed.
2031  */
2032  if (ExecCrossPartitionUpdate(context, resultRelInfo,
2033  tupleid, oldtuple, slot,
2034  canSetTag, updateCxt,
2035  &retry_slot,
2036  &inserted_tuple,
2037  &insert_destrel))
2038  {
2039  /* success! */
2040  updateCxt->updated = true;
2041  updateCxt->crossPartUpdate = true;
2042 
2043  /*
2044  * If the partitioned table being updated is referenced in foreign
2045  * keys, queue up trigger events to check that none of them were
2046  * violated. No special treatment is needed in
2047  * non-cross-partition update situations, because the leaf
2048  * partition's AR update triggers will take care of that. During
2049  * cross-partition updates implemented as delete on the source
2050  * partition followed by insert on the destination partition,
2051  * AR-UPDATE triggers of the root table (that is, the table
2052  * mentioned in the query) must be fired.
2053  *
2054  * NULL insert_destrel means that the move failed to occur, that
2055  * is, the update failed, so no need to anything in that case.
2056  */
2057  if (insert_destrel &&
2058  resultRelInfo->ri_TrigDesc &&
2059  resultRelInfo->ri_TrigDesc->trig_update_after_row)
2061  resultRelInfo,
2062  insert_destrel,
2063  tupleid, slot,
2064  inserted_tuple);
2065 
2066  return TM_Ok;
2067  }
2068 
2069  /*
2070  * No luck, a retry is needed. If running MERGE, we do not do so
2071  * here; instead let it handle that on its own rules.
2072  */
2073  if (context->relaction != NULL)
2074  return TM_Updated;
2075 
2076  /*
2077  * ExecCrossPartitionUpdate installed an updated version of the new
2078  * tuple in the retry slot; start over.
2079  */
2080  slot = retry_slot;
2081  goto lreplace;
2082  }
2083 
2084  /*
2085  * Check the constraints of the tuple. We've already checked the
2086  * partition constraint above; however, we must still ensure the tuple
2087  * passes all other constraints, so we will call ExecConstraints() and
2088  * have it validate all remaining checks.
2089  */
2090  if (resultRelationDesc->rd_att->constr)
2091  ExecConstraints(resultRelInfo, slot, estate);
2092 
2093  /*
2094  * replace the heap tuple
2095  *
2096  * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
2097  * the row to be updated is visible to that snapshot, and throw a
2098  * can't-serialize error if not. This is a special-case behavior needed
2099  * for referential integrity updates in transaction-snapshot mode
2100  * transactions.
2101  */
2102  if (lockUpdated)
2103  {
2104  MAKE_LAZY_TTS(&lazyEPQSlot, GetEPQSlot, &slotArg);
2105  lazyEPQSlotPtr = &lazyEPQSlot;
2106  }
2107  else
2108  {
2109  lazyEPQSlotPtr = NULL;
2110  }
2111  result = table_tuple_update(resultRelationDesc, tupleid, slot,
2112  estate->es_output_cid,
2113  estate->es_snapshot,
2114  estate->es_crosscheck_snapshot,
2115  true /* wait for commit */ ,
2116  &context->tmfd, &updateCxt->lockmode,
2117  &updateCxt->updateIndexes,
2118  lazyEPQSlotPtr);
2119  if (result == TM_Ok)
2120  updateCxt->updated = true;
2121 
2122  return result;
2123 }
static void ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, ResultRelInfo *sourcePartInfo, ResultRelInfo *destPartInfo, ItemPointer tupleid, TupleTableSlot *oldslot, TupleTableSlot *newslot)
static bool ExecCrossPartitionUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt, TupleTableSlot **retry_slot, TupleTableSlot **inserted_tuple, ResultRelInfo **insert_destrel)
TU_UpdateIndexes updateIndexes
LockTupleMode lockmode
static TM_Result table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes, LazyTupleTableSlot *lockedSlot)
Definition: tableam.h:1538

References TupleDescData::constr, UpdateContext::crossPartUpdate, ModifyTableContext::epqstate, EState::es_crosscheck_snapshot, EState::es_output_cid, EState::es_snapshot, ModifyTableContext::estate, ExecConstraints(), ExecCrossPartitionUpdate(), ExecCrossPartitionUpdateForeignKey(), ExecMaterializeSlot(), ExecPartitionCheck(), ExecUpdatePrepareSlot(), ExecWithCheckOptions(), GetEPQSlot(), UpdateContext::lockmode, MAKE_LAZY_TTS, NIL, RelationData::rd_att, RelationData::rd_rel, ModifyTableContext::relaction, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_WithCheckOptions, table_tuple_update(), TM_Ok, TM_Updated, ModifyTableContext::tmfd, TriggerDesc::trig_update_after_row, UpdateContext::updated, UpdateContext::updateIndexes, and WCO_RLS_UPDATE_CHECK.

Referenced by ExecMergeMatched(), and ExecUpdate().

◆ ExecUpdateEpilogue()

static void ExecUpdateEpilogue ( ModifyTableContext context,
UpdateContext updateCxt,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot slot 
)
static

Definition at line 2132 of file nodeModifyTable.c.

2135 {
2136  ModifyTableState *mtstate = context->mtstate;
2137  List *recheckIndexes = NIL;
2138 
2139  /* insert index entries for tuple if necessary */
2140  if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
2141  recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
2142  slot, context->estate,
2143  true, false,
2144  NULL, NIL,
2145  (updateCxt->updateIndexes == TU_Summarizing));
2146 
2147  /* AFTER ROW UPDATE Triggers */
2148  ExecARUpdateTriggers(context->estate, resultRelInfo,
2149  NULL, NULL,
2150  tupleid, oldtuple, slot,
2151  recheckIndexes,
2152  mtstate->operation == CMD_INSERT ?
2153  mtstate->mt_oc_transition_capture :
2154  mtstate->mt_transition_capture,
2155  false);
2156 
2157  list_free(recheckIndexes);
2158 
2159  /*
2160  * Check any WITH CHECK OPTION constraints from parent views. We are
2161  * required to do this after testing all constraints and uniqueness
2162  * violations per the SQL spec, so we do it after actually updating the
2163  * record in the heap and all indexes.
2164  *
2165  * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
2166  * are looking for at this point.
2167  */
2168  if (resultRelInfo->ri_WithCheckOptions != NIL)
2169  ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo,
2170  slot, context->estate);
2171 }
@ TU_Summarizing
Definition: tableam.h:118
@ TU_None
Definition: tableam.h:112

References CMD_INSERT, ModifyTableContext::estate, ExecARUpdateTriggers(), ExecInsertIndexTuples(), ExecWithCheckOptions(), list_free(), ModifyTableState::mt_oc_transition_capture, ModifyTableState::mt_transition_capture, ModifyTableContext::mtstate, NIL, ModifyTableState::operation, ResultRelInfo::ri_NumIndices, ResultRelInfo::ri_WithCheckOptions, TU_None, TU_Summarizing, UpdateContext::updateIndexes, and WCO_VIEW_CHECK.

Referenced by ExecMergeMatched(), and ExecUpdate().

◆ ExecUpdatePrepareSlot()

static void ExecUpdatePrepareSlot ( ResultRelInfo resultRelInfo,
TupleTableSlot slot,
EState estate 
)
static

Definition at line 1931 of file nodeModifyTable.c.

1934 {
1935  Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
1936 
1937  /*
1938  * Constraints and GENERATED expressions might reference the tableoid
1939  * column, so (re-)initialize tts_tableOid before evaluating them.
1940  */
1941  slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
1942 
1943  /*
1944  * Compute stored generated columns
1945  */
1946  if (resultRelationDesc->rd_att->constr &&
1947  resultRelationDesc->rd_att->constr->has_generated_stored)
1948  ExecComputeStoredGenerated(resultRelInfo, estate, slot,
1949  CMD_UPDATE);
1950 }

References CMD_UPDATE, TupleDescData::constr, ExecComputeStoredGenerated(), TupleConstr::has_generated_stored, RelationData::rd_att, RelationGetRelid, ResultRelInfo::ri_RelationDesc, and TupleTableSlot::tts_tableOid.

Referenced by ExecUpdate(), and ExecUpdateAct().

◆ ExecUpdatePrologue()

static bool ExecUpdatePrologue ( ModifyTableContext context,
ResultRelInfo resultRelInfo,
ItemPointer  tupleid,
HeapTuple  oldtuple,
TupleTableSlot slot,
TM_Result result 
)
static

Definition at line 1889 of file nodeModifyTable.c.

1892 {
1893  Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
1894 
1895  if (result)
1896  *result = TM_Ok;
1897 
1898  ExecMaterializeSlot(slot);
1899 
1900  /*
1901  * Open the table's indexes, if we have not done so already, so that we
1902  * can add new index entries for the updated tuple.
1903  */
1904  if (resultRelationDesc->rd_rel->relhasindex &&
1905  resultRelInfo->ri_IndexRelationDescs == NULL)
1906  ExecOpenIndices(resultRelInfo, false);
1907 
1908  /* BEFORE ROW UPDATE triggers */
1909  if (resultRelInfo->ri_TrigDesc &&
1910  resultRelInfo->ri_TrigDesc->trig_update_before_row)
1911  {
1912  /* Flush any pending inserts, so rows are visible to the triggers */
1914  ExecPendingInserts(context->estate);
1915 
1916  return ExecBRUpdateTriggers(context->estate, context->epqstate,
1917  resultRelInfo, tupleid, oldtuple, slot,
1918  result, &context->tmfd);
1919  }
1920 
1921  return true;
1922 }
bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot *newslot, TM_Result *tmresult, TM_FailureData *tmfd)
Definition: trigger.c:2945

References ModifyTableContext::epqstate, EState::es_insert_pending_result_relations, ModifyTableContext::estate, ExecBRUpdateTriggers(), ExecMaterializeSlot(), ExecOpenIndices(), ExecPendingInserts(), NIL, RelationData::rd_rel, ResultRelInfo::ri_IndexRelationDescs, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, TM_Ok, ModifyTableContext::tmfd, and TriggerDesc::trig_update_before_row.

Referenced by ExecMergeMatched(), and ExecUpdate().

◆ fireASTriggers()

static void fireASTriggers ( ModifyTableState node)
static

Definition at line 3376 of file nodeModifyTable.c.

3377 {
3378  ModifyTable *plan = (ModifyTable *) node->ps.plan;
3379  ResultRelInfo *resultRelInfo = node->rootResultRelInfo;
3380 
3381  switch (node->operation)
3382  {
3383  case CMD_INSERT:
3384  if (plan->onConflictAction == ONCONFLICT_UPDATE)
3386  resultRelInfo,
3387  node->mt_oc_transition_capture);
3388  ExecASInsertTriggers(node->ps.state, resultRelInfo,
3389  node->mt_transition_capture);
3390  break;
3391  case CMD_UPDATE:
3392  ExecASUpdateTriggers(node->ps.state, resultRelInfo,
3393  node->mt_transition_capture);
3394  break;
3395  case CMD_DELETE:
3396  ExecASDeleteTriggers(node->ps.state, resultRelInfo,
3397  node->mt_transition_capture);
3398  break;
3399  case CMD_MERGE:
3400  if (node->mt_merge_subcommands & MERGE_DELETE)
3401  ExecASDeleteTriggers(node->ps.state, resultRelInfo,
3402  node->mt_transition_capture);
3403  if (node->mt_merge_subcommands & MERGE_UPDATE)
3404  ExecASUpdateTriggers(node->ps.state, resultRelInfo,
3405  node->mt_transition_capture);
3406  if (node->mt_merge_subcommands & MERGE_INSERT)
3407  ExecASInsertTriggers(node->ps.state, resultRelInfo,
3408  node->mt_transition_capture);
3409  break;
3410  default:
3411  elog(ERROR, "unknown operation");
3412  break;
3413  }
3414 }
void ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture)
Definition: trigger.c:2927
void ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture)
Definition: trigger.c:2674
void ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture)
Definition: trigger.c:2456