Thanks to visit codestin.com
Credit goes to doxygen.postgresql.org

PostgreSQL Source Code git master
execReplication.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * execReplication.c
4 * miscellaneous executor routines for logical replication
5 *
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 * IDENTIFICATION
10 * src/backend/executor/execReplication.c
11 *
12 *-------------------------------------------------------------------------
13 */
14
15#include "postgres.h"
16
17#include "access/commit_ts.h"
18#include "access/genam.h"
19#include "access/gist.h"
20#include "access/relscan.h"
21#include "access/tableam.h"
22#include "access/transam.h"
23#include "access/xact.h"
24#include "access/heapam.h"
25#include "catalog/pg_am_d.h"
26#include "commands/trigger.h"
27#include "executor/executor.h"
31#include "storage/lmgr.h"
32#include "utils/builtins.h"
33#include "utils/lsyscache.h"
34#include "utils/rel.h"
35#include "utils/snapmgr.h"
36#include "utils/syscache.h"
37#include "utils/typcache.h"
38
39
40static bool tuples_equal(TupleTableSlot *slot1, TupleTableSlot *slot2,
41 TypeCacheEntry **eq, Bitmapset *columns);
42
43/*
44 * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that
45 * is setup to match 'rel' (*NOT* idxrel!).
46 *
47 * Returns how many columns to use for the index scan.
48 *
49 * This is not generic routine, idxrel must be PK, RI, or an index that can be
50 * used for REPLICA IDENTITY FULL table. See FindUsableIndexForReplicaIdentityFull()
51 * for details.
52 *
53 * By definition, replication identity of a rel meets all limitations associated
54 * with that. Note that any other index could also meet these limitations.
55 */
56static int
58 TupleTableSlot *searchslot)
59{
60 int index_attoff;
61 int skey_attoff = 0;
62 Datum indclassDatum;
63 oidvector *opclass;
64 int2vector *indkey = &idxrel->rd_index->indkey;
65
66 indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, idxrel->rd_indextuple,
67 Anum_pg_index_indclass);
68 opclass = (oidvector *) DatumGetPointer(indclassDatum);
69
70 /* Build scankey for every non-expression attribute in the index. */
71 for (index_attoff = 0; index_attoff < IndexRelationGetNumberOfKeyAttributes(idxrel);
72 index_attoff++)
73 {
74 Oid operator;
75 Oid optype;
76 Oid opfamily;
77 RegProcedure regop;
78 int table_attno = indkey->values[index_attoff];
79 StrategyNumber eq_strategy;
80
81 if (!AttributeNumberIsValid(table_attno))
82 {
83 /*
84 * XXX: Currently, we don't support expressions in the scan key,
85 * see code below.
86 */
87 continue;
88 }
89
90 /*
91 * Load the operator info. We need this to get the equality operator
92 * function for the scan key.
93 */
94 optype = get_opclass_input_type(opclass->values[index_attoff]);
95 opfamily = get_opclass_family(opclass->values[index_attoff]);
96 eq_strategy = IndexAmTranslateCompareType(COMPARE_EQ, idxrel->rd_rel->relam, opfamily, false);
97 operator = get_opfamily_member(opfamily, optype,
98 optype,
99 eq_strategy);
100
101 if (!OidIsValid(operator))
102 elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
103 eq_strategy, optype, optype, opfamily);
104
105 regop = get_opcode(operator);
106
107 /* Initialize the scankey. */
108 ScanKeyInit(&skey[skey_attoff],
109 index_attoff + 1,
110 eq_strategy,
111 regop,
112 searchslot->tts_values[table_attno - 1]);
113
114 skey[skey_attoff].sk_collation = idxrel->rd_indcollation[index_attoff];
115
116 /* Check for null value. */
117 if (searchslot->tts_isnull[table_attno - 1])
118 skey[skey_attoff].sk_flags |= (SK_ISNULL | SK_SEARCHNULL);
119
120 skey_attoff++;
121 }
122
123 /* There must always be at least one attribute for the index scan. */
124 Assert(skey_attoff > 0);
125
126 return skey_attoff;
127}
128
129
130/*
131 * Helper function to check if it is necessary to re-fetch and lock the tuple
132 * due to concurrent modifications. This function should be called after
133 * invoking table_tuple_lock.
134 */
135static bool
137{
138 bool refetch = false;
139
140 switch (res)
141 {
142 case TM_Ok:
143 break;
144 case TM_Updated:
145 /* XXX: Improve handling here */
147 ereport(LOG,
149 errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying")));
150 else
151 ereport(LOG,
153 errmsg("concurrent update, retrying")));
154 refetch = true;
155 break;
156 case TM_Deleted:
157 /* XXX: Improve handling here */
158 ereport(LOG,
160 errmsg("concurrent delete, retrying")));
161 refetch = true;
162 break;
163 case TM_Invisible:
164 elog(ERROR, "attempted to lock invisible tuple");
165 break;
166 default:
167 elog(ERROR, "unexpected table_tuple_lock status: %u", res);
168 break;
169 }
170
171 return refetch;
172}
173
174/*
175 * Search the relation 'rel' for tuple using the index.
176 *
177 * If a matching tuple is found, lock it with lockmode, fill the slot with its
178 * contents, and return true. Return false otherwise.
179 */
180bool
182 LockTupleMode lockmode,
183 TupleTableSlot *searchslot,
184 TupleTableSlot *outslot)
185{
187 int skey_attoff;
188 IndexScanDesc scan;
189 SnapshotData snap;
190 TransactionId xwait;
191 Relation idxrel;
192 bool found;
193 TypeCacheEntry **eq = NULL;
194 bool isIdxSafeToSkipDuplicates;
195
196 /* Open the index. */
197 idxrel = index_open(idxoid, RowExclusiveLock);
198
199 isIdxSafeToSkipDuplicates = (GetRelationIdentityOrPK(rel) == idxoid);
200
201 InitDirtySnapshot(snap);
202
203 /* Build scan key. */
204 skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot);
205
206 /* Start an index scan. */
207 scan = index_beginscan(rel, idxrel, &snap, NULL, skey_attoff, 0);
208
209retry:
210 found = false;
211
212 index_rescan(scan, skey, skey_attoff, NULL, 0);
213
214 /* Try to find the tuple */
215 while (index_getnext_slot(scan, ForwardScanDirection, outslot))
216 {
217 /*
218 * Avoid expensive equality check if the index is primary key or
219 * replica identity index.
220 */
221 if (!isIdxSafeToSkipDuplicates)
222 {
223 if (eq == NULL)
224 eq = palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts);
225
226 if (!tuples_equal(outslot, searchslot, eq, NULL))
227 continue;
228 }
229
230 ExecMaterializeSlot(outslot);
231
232 xwait = TransactionIdIsValid(snap.xmin) ?
233 snap.xmin : snap.xmax;
234
235 /*
236 * If the tuple is locked, wait for locking transaction to finish and
237 * retry.
238 */
239 if (TransactionIdIsValid(xwait))
240 {
241 XactLockTableWait(xwait, NULL, NULL, XLTW_None);
242 goto retry;
243 }
244
245 /* Found our tuple and it's not locked */
246 found = true;
247 break;
248 }
249
250 /* Found tuple, try to lock it in the lockmode. */
251 if (found)
252 {
253 TM_FailureData tmfd;
254 TM_Result res;
255
257
258 res = table_tuple_lock(rel, &(outslot->tts_tid), GetActiveSnapshot(),
259 outslot,
260 GetCurrentCommandId(false),
261 lockmode,
263 0 /* don't follow updates */ ,
264 &tmfd);
265
267
268 if (should_refetch_tuple(res, &tmfd))
269 goto retry;
270 }
271
272 index_endscan(scan);
273
274 /* Don't release lock until commit. */
275 index_close(idxrel, NoLock);
276
277 return found;
278}
279
280/*
281 * Compare the tuples in the slots by checking if they have equal values.
282 *
283 * If 'columns' is not null, only the columns specified within it will be
284 * considered for the equality check, ignoring all other columns.
285 */
286static bool
288 TypeCacheEntry **eq, Bitmapset *columns)
289{
290 int attrnum;
291
293 slot2->tts_tupleDescriptor->natts);
294
295 slot_getallattrs(slot1);
296 slot_getallattrs(slot2);
297
298 /* Check equality of the attributes. */
299 for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
300 {
302 TypeCacheEntry *typentry;
303
304 att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
305
306 /*
307 * Ignore dropped and generated columns as the publisher doesn't send
308 * those
309 */
310 if (att->attisdropped || att->attgenerated)
311 continue;
312
313 /*
314 * Ignore columns that are not listed for checking.
315 */
316 if (columns &&
318 columns))
319 continue;
320
321 /*
322 * If one value is NULL and other is not, then they are certainly not
323 * equal
324 */
325 if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
326 return false;
327
328 /*
329 * If both are NULL, they can be considered equal.
330 */
331 if (slot1->tts_isnull[attrnum] || slot2->tts_isnull[attrnum])
332 continue;
333
334 typentry = eq[attrnum];
335 if (typentry == NULL)
336 {
337 typentry = lookup_type_cache(att->atttypid,
339 if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
341 (errcode(ERRCODE_UNDEFINED_FUNCTION),
342 errmsg("could not identify an equality operator for type %s",
343 format_type_be(att->atttypid))));
344 eq[attrnum] = typentry;
345 }
346
348 att->attcollation,
349 slot1->tts_values[attrnum],
350 slot2->tts_values[attrnum])))
351 return false;
352 }
353
354 return true;
355}
356
357/*
358 * Search the relation 'rel' for tuple using the sequential scan.
359 *
360 * If a matching tuple is found, lock it with lockmode, fill the slot with its
361 * contents, and return true. Return false otherwise.
362 *
363 * Note that this stops on the first matching tuple.
364 *
365 * This can obviously be quite slow on tables that have more than few rows.
366 */
367bool
369 TupleTableSlot *searchslot, TupleTableSlot *outslot)
370{
371 TupleTableSlot *scanslot;
372 TableScanDesc scan;
373 SnapshotData snap;
374 TypeCacheEntry **eq;
375 TransactionId xwait;
376 bool found;
378
380
381 eq = palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts);
382
383 /* Start a heap scan. */
384 InitDirtySnapshot(snap);
385 scan = table_beginscan(rel, &snap, 0, NULL);
386 scanslot = table_slot_create(rel, NULL);
387
388retry:
389 found = false;
390
391 table_rescan(scan, NULL);
392
393 /* Try to find the tuple */
394 while (table_scan_getnextslot(scan, ForwardScanDirection, scanslot))
395 {
396 if (!tuples_equal(scanslot, searchslot, eq, NULL))
397 continue;
398
399 found = true;
400 ExecCopySlot(outslot, scanslot);
401
402 xwait = TransactionIdIsValid(snap.xmin) ?
403 snap.xmin : snap.xmax;
404
405 /*
406 * If the tuple is locked, wait for locking transaction to finish and
407 * retry.
408 */
409 if (TransactionIdIsValid(xwait))
410 {
411 XactLockTableWait(xwait, NULL, NULL, XLTW_None);
412 goto retry;
413 }
414
415 /* Found our tuple and it's not locked */
416 break;
417 }
418
419 /* Found tuple, try to lock it in the lockmode. */
420 if (found)
421 {
422 TM_FailureData tmfd;
423 TM_Result res;
424
426
427 res = table_tuple_lock(rel, &(outslot->tts_tid), GetActiveSnapshot(),
428 outslot,
429 GetCurrentCommandId(false),
430 lockmode,
432 0 /* don't follow updates */ ,
433 &tmfd);
434
436
437 if (should_refetch_tuple(res, &tmfd))
438 goto retry;
439 }
440
441 table_endscan(scan);
443
444 return found;
445}
446
447/*
448 * Build additional index information necessary for conflict detection.
449 */
450static void
451BuildConflictIndexInfo(ResultRelInfo *resultRelInfo, Oid conflictindex)
452{
453 for (int i = 0; i < resultRelInfo->ri_NumIndices; i++)
454 {
455 Relation indexRelation = resultRelInfo->ri_IndexRelationDescs[i];
456 IndexInfo *indexRelationInfo = resultRelInfo->ri_IndexRelationInfo[i];
457
458 if (conflictindex != RelationGetRelid(indexRelation))
459 continue;
460
461 /*
462 * This Assert will fail if BuildSpeculativeIndexInfo() is called
463 * twice for the given index.
464 */
465 Assert(indexRelationInfo->ii_UniqueOps == NULL);
466
467 BuildSpeculativeIndexInfo(indexRelation, indexRelationInfo);
468 }
469}
470
471/*
472 * If the tuple is recently dead and was deleted by a transaction with a newer
473 * commit timestamp than previously recorded, update the associated transaction
474 * ID, commit time, and origin. This helps ensure that conflict detection uses
475 * the most recent and relevant deletion metadata.
476 */
477static void
479 TransactionId oldestxmin,
480 TransactionId *delete_xid,
481 TimestampTz *delete_time,
482 RepOriginId *delete_origin)
483{
485 HeapTuple tuple;
486 Buffer buf;
487 bool recently_dead = false;
488 TransactionId xmax;
489 TimestampTz localts;
490 RepOriginId localorigin;
491
492 hslot = (BufferHeapTupleTableSlot *) scanslot;
493
494 tuple = ExecFetchSlotHeapTuple(scanslot, false, NULL);
495 buf = hslot->buffer;
496
498
499 /*
500 * We do not consider HEAPTUPLE_DEAD status because it indicates either
501 * tuples whose inserting transaction was aborted (meaning there is no
502 * commit timestamp or origin), or tuples deleted by a transaction older
503 * than oldestxmin, making it safe to ignore them during conflict
504 * detection (See comments atop worker.c for details).
505 */
506 if (HeapTupleSatisfiesVacuum(tuple, oldestxmin, buf) == HEAPTUPLE_RECENTLY_DEAD)
507 recently_dead = true;
508
510
511 if (!recently_dead)
512 return;
513
514 xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
515 if (!TransactionIdIsValid(xmax))
516 return;
517
518 /* Select the dead tuple with the most recent commit timestamp */
519 if (TransactionIdGetCommitTsData(xmax, &localts, &localorigin) &&
520 TimestampDifferenceExceeds(*delete_time, localts, 0))
521 {
522 *delete_xid = xmax;
523 *delete_time = localts;
524 *delete_origin = localorigin;
525 }
526}
527
528/*
529 * Searches the relation 'rel' for the most recently deleted tuple that matches
530 * the values in 'searchslot' and is not yet removable by VACUUM. The function
531 * returns the transaction ID, origin, and commit timestamp of the transaction
532 * that deleted this tuple.
533 *
534 * 'oldestxmin' acts as a cutoff transaction ID. Tuples deleted by transactions
535 * with IDs >= 'oldestxmin' are considered recently dead and are eligible for
536 * conflict detection.
537 *
538 * Instead of stopping at the first match, we scan all matching dead tuples to
539 * identify most recent deletion. This is crucial because only the latest
540 * deletion is relevant for resolving conflicts.
541 *
542 * For example, consider a scenario on the subscriber where a row is deleted,
543 * re-inserted, and then deleted again only on the subscriber:
544 *
545 * - (pk, 1) - deleted at 9:00,
546 * - (pk, 1) - deleted at 9:02,
547 *
548 * Now, a remote update arrives: (pk, 1) -> (pk, 2), timestamped at 9:01.
549 *
550 * If we mistakenly return the older deletion (9:00), the system may wrongly
551 * apply the remote update using a last-update-wins strategy. Instead, we must
552 * recognize the more recent deletion at 9:02 and skip the update. See
553 * comments atop worker.c for details. Note, as of now, conflict resolution
554 * is not implemented. Consequently, the system may incorrectly report the
555 * older tuple as the conflicted one, leading to misleading results.
556 *
557 * The commit timestamp of the deleting transaction is used to determine which
558 * tuple was deleted most recently.
559 */
560bool
562 TransactionId oldestxmin,
563 TransactionId *delete_xid,
564 RepOriginId *delete_origin,
565 TimestampTz *delete_time)
566{
567 TupleTableSlot *scanslot;
568 TableScanDesc scan;
569 TypeCacheEntry **eq;
570 Bitmapset *indexbitmap;
572
573 Assert(equalTupleDescs(desc, searchslot->tts_tupleDescriptor));
574
575 *delete_xid = InvalidTransactionId;
576 *delete_origin = InvalidRepOriginId;
577 *delete_time = 0;
578
579 /*
580 * If the relation has a replica identity key or a primary key that is
581 * unusable for locating deleted tuples (see
582 * IsIndexUsableForFindingDeletedTuple), a full table scan becomes
583 * necessary. In such cases, comparing the entire tuple is not required,
584 * since the remote tuple might not include all column values. Instead,
585 * the indexed columns alone are sufficient to identify the target tuple
586 * (see logicalrep_rel_mark_updatable).
587 */
588 indexbitmap = RelationGetIndexAttrBitmap(rel,
590
591 /* fallback to PK if no replica identity */
592 if (!indexbitmap)
593 indexbitmap = RelationGetIndexAttrBitmap(rel,
595
596 eq = palloc0(sizeof(*eq) * searchslot->tts_tupleDescriptor->natts);
597
598 /*
599 * Start a heap scan using SnapshotAny to identify dead tuples that are
600 * not visible under a standard MVCC snapshot. Tuples from transactions
601 * not yet committed or those just committed prior to the scan are
602 * excluded in update_most_recent_deletion_info().
603 */
604 scan = table_beginscan(rel, SnapshotAny, 0, NULL);
605 scanslot = table_slot_create(rel, NULL);
606
607 table_rescan(scan, NULL);
608
609 /* Try to find the tuple */
610 while (table_scan_getnextslot(scan, ForwardScanDirection, scanslot))
611 {
612 if (!tuples_equal(scanslot, searchslot, eq, indexbitmap))
613 continue;
614
615 update_most_recent_deletion_info(scanslot, oldestxmin, delete_xid,
616 delete_time, delete_origin);
617 }
618
619 table_endscan(scan);
621
622 return *delete_time != 0;
623}
624
625/*
626 * Similar to RelationFindDeletedTupleInfoSeq() but using index scan to locate
627 * the deleted tuple.
628 */
629bool
631 TupleTableSlot *searchslot,
632 TransactionId oldestxmin,
633 TransactionId *delete_xid,
634 RepOriginId *delete_origin,
635 TimestampTz *delete_time)
636{
637 Relation idxrel;
639 int skey_attoff;
640 IndexScanDesc scan;
641 TupleTableSlot *scanslot;
642 TypeCacheEntry **eq = NULL;
643 bool isIdxSafeToSkipDuplicates;
645
646 Assert(equalTupleDescs(desc, searchslot->tts_tupleDescriptor));
647 Assert(OidIsValid(idxoid));
648
649 *delete_xid = InvalidTransactionId;
650 *delete_time = 0;
651 *delete_origin = InvalidRepOriginId;
652
653 isIdxSafeToSkipDuplicates = (GetRelationIdentityOrPK(rel) == idxoid);
654
655 scanslot = table_slot_create(rel, NULL);
656
657 idxrel = index_open(idxoid, RowExclusiveLock);
658
659 /* Build scan key. */
660 skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot);
661
662 /*
663 * Start an index scan using SnapshotAny to identify dead tuples that are
664 * not visible under a standard MVCC snapshot. Tuples from transactions
665 * not yet committed or those just committed prior to the scan are
666 * excluded in update_most_recent_deletion_info().
667 */
668 scan = index_beginscan(rel, idxrel, SnapshotAny, NULL, skey_attoff, 0);
669
670 index_rescan(scan, skey, skey_attoff, NULL, 0);
671
672 /* Try to find the tuple */
673 while (index_getnext_slot(scan, ForwardScanDirection, scanslot))
674 {
675 /*
676 * Avoid expensive equality check if the index is primary key or
677 * replica identity index.
678 */
679 if (!isIdxSafeToSkipDuplicates)
680 {
681 if (eq == NULL)
682 eq = palloc0(sizeof(*eq) * scanslot->tts_tupleDescriptor->natts);
683
684 if (!tuples_equal(scanslot, searchslot, eq, NULL))
685 continue;
686 }
687
688 update_most_recent_deletion_info(scanslot, oldestxmin, delete_xid,
689 delete_time, delete_origin);
690 }
691
692 index_endscan(scan);
693
694 index_close(idxrel, NoLock);
695
697
698 return *delete_time != 0;
699}
700
701/*
702 * Find the tuple that violates the passed unique index (conflictindex).
703 *
704 * If the conflicting tuple is found return true, otherwise false.
705 *
706 * We lock the tuple to avoid getting it deleted before the caller can fetch
707 * the required information. Note that if the tuple is deleted before a lock
708 * is acquired, we will retry to find the conflicting tuple again.
709 */
710static bool
711FindConflictTuple(ResultRelInfo *resultRelInfo, EState *estate,
712 Oid conflictindex, TupleTableSlot *slot,
713 TupleTableSlot **conflictslot)
714{
715 Relation rel = resultRelInfo->ri_RelationDesc;
716 ItemPointerData conflictTid;
717 TM_FailureData tmfd;
718 TM_Result res;
719
720 *conflictslot = NULL;
721
722 /*
723 * Build additional information required to check constraints violations.
724 * See check_exclusion_or_unique_constraint().
725 */
726 BuildConflictIndexInfo(resultRelInfo, conflictindex);
727
728retry:
729 if (ExecCheckIndexConstraints(resultRelInfo, slot, estate,
730 &conflictTid, &slot->tts_tid,
731 list_make1_oid(conflictindex)))
732 {
733 if (*conflictslot)
734 ExecDropSingleTupleTableSlot(*conflictslot);
735
736 *conflictslot = NULL;
737 return false;
738 }
739
740 *conflictslot = table_slot_create(rel, NULL);
741
743
744 res = table_tuple_lock(rel, &conflictTid, GetActiveSnapshot(),
745 *conflictslot,
746 GetCurrentCommandId(false),
749 0 /* don't follow updates */ ,
750 &tmfd);
751
753
754 if (should_refetch_tuple(res, &tmfd))
755 goto retry;
756
757 return true;
758}
759
760/*
761 * Check all the unique indexes in 'recheckIndexes' for conflict with the
762 * tuple in 'remoteslot' and report if found.
763 */
764static void
766 ConflictType type, List *recheckIndexes,
767 TupleTableSlot *searchslot, TupleTableSlot *remoteslot)
768{
769 List *conflicttuples = NIL;
770 TupleTableSlot *conflictslot;
771
772 /* Check all the unique indexes for conflicts */
773 foreach_oid(uniqueidx, resultRelInfo->ri_onConflictArbiterIndexes)
774 {
775 if (list_member_oid(recheckIndexes, uniqueidx) &&
776 FindConflictTuple(resultRelInfo, estate, uniqueidx, remoteslot,
777 &conflictslot))
778 {
780
781 conflicttuple->slot = conflictslot;
782 conflicttuple->indexoid = uniqueidx;
783
784 GetTupleTransactionInfo(conflictslot, &conflicttuple->xmin,
785 &conflicttuple->origin, &conflicttuple->ts);
786
787 conflicttuples = lappend(conflicttuples, conflicttuple);
788 }
789 }
790
791 /* Report the conflict, if found */
792 if (conflicttuples)
793 ReportApplyConflict(estate, resultRelInfo, ERROR,
794 list_length(conflicttuples) > 1 ? CT_MULTIPLE_UNIQUE_CONFLICTS : type,
795 searchslot, remoteslot, conflicttuples);
796}
797
798/*
799 * Insert tuple represented in the slot to the relation, update the indexes,
800 * and execute any constraints and per-row triggers.
801 *
802 * Caller is responsible for opening the indexes.
803 */
804void
806 EState *estate, TupleTableSlot *slot)
807{
808 bool skip_tuple = false;
809 Relation rel = resultRelInfo->ri_RelationDesc;
810
811 /* For now we support only tables. */
812 Assert(rel->rd_rel->relkind == RELKIND_RELATION);
813
815
816 /* BEFORE ROW INSERT Triggers */
817 if (resultRelInfo->ri_TrigDesc &&
818 resultRelInfo->ri_TrigDesc->trig_insert_before_row)
819 {
820 if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
821 skip_tuple = true; /* "do nothing" */
822 }
823
824 if (!skip_tuple)
825 {
826 List *recheckIndexes = NIL;
827 List *conflictindexes;
828 bool conflict = false;
829
830 /* Compute stored generated columns */
831 if (rel->rd_att->constr &&
833 ExecComputeStoredGenerated(resultRelInfo, estate, slot,
834 CMD_INSERT);
835
836 /* Check the constraints of the tuple */
837 if (rel->rd_att->constr)
838 ExecConstraints(resultRelInfo, slot, estate);
839 if (rel->rd_rel->relispartition)
840 ExecPartitionCheck(resultRelInfo, slot, estate, true);
841
842 /* OK, store the tuple and create index entries for it */
843 simple_table_tuple_insert(resultRelInfo->ri_RelationDesc, slot);
844
845 conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
846
847 if (resultRelInfo->ri_NumIndices > 0)
848 recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
849 slot, estate, false,
850 conflictindexes ? true : false,
851 &conflict,
852 conflictindexes, false);
853
854 /*
855 * Checks the conflict indexes to fetch the conflicting local row and
856 * reports the conflict. We perform this check here, instead of
857 * performing an additional index scan before the actual insertion and
858 * reporting the conflict if any conflicting rows are found. This is
859 * to avoid the overhead of executing the extra scan for each INSERT
860 * operation, even when no conflict arises, which could introduce
861 * significant overhead to replication, particularly in cases where
862 * conflicts are rare.
863 *
864 * XXX OTOH, this could lead to clean-up effort for dead tuples added
865 * in heap and index in case of conflicts. But as conflicts shouldn't
866 * be a frequent thing so we preferred to save the performance
867 * overhead of extra scan before each insertion.
868 */
869 if (conflict)
870 CheckAndReportConflict(resultRelInfo, estate, CT_INSERT_EXISTS,
871 recheckIndexes, NULL, slot);
872
873 /* AFTER ROW INSERT Triggers */
874 ExecARInsertTriggers(estate, resultRelInfo, slot,
875 recheckIndexes, NULL);
876
877 /*
878 * XXX we should in theory pass a TransitionCaptureState object to the
879 * above to capture transition tuples, but after statement triggers
880 * don't actually get fired by replication yet anyway
881 */
882
883 list_free(recheckIndexes);
884 }
885}
886
887/*
888 * Find the searchslot tuple and update it with data in the slot,
889 * update the indexes, and execute any constraints and per-row triggers.
890 *
891 * Caller is responsible for opening the indexes.
892 */
893void
895 EState *estate, EPQState *epqstate,
896 TupleTableSlot *searchslot, TupleTableSlot *slot)
897{
898 bool skip_tuple = false;
899 Relation rel = resultRelInfo->ri_RelationDesc;
900 ItemPointer tid = &(searchslot->tts_tid);
901
902 /*
903 * We support only non-system tables, with
904 * check_publication_add_relation() accountable.
905 */
906 Assert(rel->rd_rel->relkind == RELKIND_RELATION);
908
910
911 /* BEFORE ROW UPDATE Triggers */
912 if (resultRelInfo->ri_TrigDesc &&
913 resultRelInfo->ri_TrigDesc->trig_update_before_row)
914 {
915 if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
916 tid, NULL, slot, NULL, NULL, false))
917 skip_tuple = true; /* "do nothing" */
918 }
919
920 if (!skip_tuple)
921 {
922 List *recheckIndexes = NIL;
923 TU_UpdateIndexes update_indexes;
924 List *conflictindexes;
925 bool conflict = false;
926
927 /* Compute stored generated columns */
928 if (rel->rd_att->constr &&
930 ExecComputeStoredGenerated(resultRelInfo, estate, slot,
931 CMD_UPDATE);
932
933 /* Check the constraints of the tuple */
934 if (rel->rd_att->constr)
935 ExecConstraints(resultRelInfo, slot, estate);
936 if (rel->rd_rel->relispartition)
937 ExecPartitionCheck(resultRelInfo, slot, estate, true);
938
939 simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
940 &update_indexes);
941
942 conflictindexes = resultRelInfo->ri_onConflictArbiterIndexes;
943
944 if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
945 recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
946 slot, estate, true,
947 conflictindexes ? true : false,
948 &conflict, conflictindexes,
949 (update_indexes == TU_Summarizing));
950
951 /*
952 * Refer to the comments above the call to CheckAndReportConflict() in
953 * ExecSimpleRelationInsert to understand why this check is done at
954 * this point.
955 */
956 if (conflict)
957 CheckAndReportConflict(resultRelInfo, estate, CT_UPDATE_EXISTS,
958 recheckIndexes, searchslot, slot);
959
960 /* AFTER ROW UPDATE Triggers */
961 ExecARUpdateTriggers(estate, resultRelInfo,
962 NULL, NULL,
963 tid, NULL, slot,
964 recheckIndexes, NULL, false);
965
966 list_free(recheckIndexes);
967 }
968}
969
970/*
971 * Find the searchslot tuple and delete it, and execute any constraints
972 * and per-row triggers.
973 *
974 * Caller is responsible for opening the indexes.
975 */
976void
978 EState *estate, EPQState *epqstate,
979 TupleTableSlot *searchslot)
980{
981 bool skip_tuple = false;
982 Relation rel = resultRelInfo->ri_RelationDesc;
983 ItemPointer tid = &searchslot->tts_tid;
984
986
987 /* BEFORE ROW DELETE Triggers */
988 if (resultRelInfo->ri_TrigDesc &&
989 resultRelInfo->ri_TrigDesc->trig_delete_before_row)
990 {
991 skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
992 tid, NULL, NULL, NULL, NULL, false);
993 }
994
995 if (!skip_tuple)
996 {
997 /* OK, delete the tuple */
998 simple_table_tuple_delete(rel, tid, estate->es_snapshot);
999
1000 /* AFTER ROW DELETE Triggers */
1001 ExecARDeleteTriggers(estate, resultRelInfo,
1002 tid, NULL, NULL, false);
1003 }
1004}
1005
1006/*
1007 * Check if command can be executed with current replica identity.
1008 */
1009void
1011{
1012 PublicationDesc pubdesc;
1013
1014 /*
1015 * Skip checking the replica identity for partitioned tables, because the
1016 * operations are actually performed on the leaf partitions.
1017 */
1018 if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1019 return;
1020
1021 /* We only need to do checks for UPDATE and DELETE. */
1022 if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
1023 return;
1024
1025 /*
1026 * It is only safe to execute UPDATE/DELETE if the relation does not
1027 * publish UPDATEs or DELETEs, or all the following conditions are
1028 * satisfied:
1029 *
1030 * 1. All columns, referenced in the row filters from publications which
1031 * the relation is in, are valid - i.e. when all referenced columns are
1032 * part of REPLICA IDENTITY.
1033 *
1034 * 2. All columns, referenced in the column lists are valid - i.e. when
1035 * all columns referenced in the REPLICA IDENTITY are covered by the
1036 * column list.
1037 *
1038 * 3. All generated columns in REPLICA IDENTITY of the relation, are valid
1039 * - i.e. when all these generated columns are published.
1040 *
1041 * XXX We could optimize it by first checking whether any of the
1042 * publications have a row filter or column list for this relation, or if
1043 * the relation contains a generated column. If none of these exist and
1044 * the relation has replica identity then we can avoid building the
1045 * descriptor but as this happens only one time it doesn't seem worth the
1046 * additional complexity.
1047 */
1048 RelationBuildPublicationDesc(rel, &pubdesc);
1049 if (cmd == CMD_UPDATE && !pubdesc.rf_valid_for_update)
1050 ereport(ERROR,
1051 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1052 errmsg("cannot update table \"%s\"",
1054 errdetail("Column used in the publication WHERE expression is not part of the replica identity.")));
1055 else if (cmd == CMD_UPDATE && !pubdesc.cols_valid_for_update)
1056 ereport(ERROR,
1057 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1058 errmsg("cannot update table \"%s\"",
1060 errdetail("Column list used by the publication does not cover the replica identity.")));
1061 else if (cmd == CMD_UPDATE && !pubdesc.gencols_valid_for_update)
1062 ereport(ERROR,
1063 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1064 errmsg("cannot update table \"%s\"",
1066 errdetail("Replica identity must not contain unpublished generated columns.")));
1067 else if (cmd == CMD_DELETE && !pubdesc.rf_valid_for_delete)
1068 ereport(ERROR,
1069 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1070 errmsg("cannot delete from table \"%s\"",
1072 errdetail("Column used in the publication WHERE expression is not part of the replica identity.")));
1073 else if (cmd == CMD_DELETE && !pubdesc.cols_valid_for_delete)
1074 ereport(ERROR,
1075 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1076 errmsg("cannot delete from table \"%s\"",
1078 errdetail("Column list used by the publication does not cover the replica identity.")));
1079 else if (cmd == CMD_DELETE && !pubdesc.gencols_valid_for_delete)
1080 ereport(ERROR,
1081 (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1082 errmsg("cannot delete from table \"%s\"",
1084 errdetail("Replica identity must not contain unpublished generated columns.")));
1085
1086 /* If relation has replica identity we are always good. */
1088 return;
1089
1090 /* REPLICA IDENTITY FULL is also good for UPDATE/DELETE. */
1091 if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
1092 return;
1093
1094 /*
1095 * This is UPDATE/DELETE and there is no replica identity.
1096 *
1097 * Check if the table publishes UPDATES or DELETES.
1098 */
1099 if (cmd == CMD_UPDATE && pubdesc.pubactions.pubupdate)
1100 ereport(ERROR,
1101 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1102 errmsg("cannot update table \"%s\" because it does not have a replica identity and publishes updates",
1104 errhint("To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.")));
1105 else if (cmd == CMD_DELETE && pubdesc.pubactions.pubdelete)
1106 ereport(ERROR,
1107 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1108 errmsg("cannot delete from table \"%s\" because it does not have a replica identity and publishes deletes",
1110 errhint("To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.")));
1111}
1112
1113
1114/*
1115 * Check if we support writing into specific relkind.
1116 *
1117 * The nspname and relname are only needed for error reporting.
1118 */
1119void
1120CheckSubscriptionRelkind(char relkind, const char *nspname,
1121 const char *relname)
1122{
1123 if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
1124 ereport(ERROR,
1125 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1126 errmsg("cannot use relation \"%s.%s\" as logical replication target",
1127 nspname, relname),
1129}
StrategyNumber IndexAmTranslateCompareType(CompareType cmptype, Oid amoid, Oid opfamily, bool missing_ok)
Definition: amapi.c:161
#define AttributeNumberIsValid(attributeNumber)
Definition: attnum.h:34
bool TimestampDifferenceExceeds(TimestampTz start_time, TimestampTz stop_time, int msec)
Definition: timestamp.c:1781
bool bms_is_member(int x, const Bitmapset *a)
Definition: bitmapset.c:510
int Buffer
Definition: buf.h:23
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5572
#define BUFFER_LOCK_UNLOCK
Definition: bufmgr.h:196
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:197
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:223
regproc RegProcedure
Definition: c.h:656
uint32 TransactionId
Definition: c.h:658
#define OidIsValid(objectId)
Definition: c.h:775
bool IsCatalogRelation(Relation relation)
Definition: catalog.c:104
@ COMPARE_EQ
Definition: cmptype.h:36
bool TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts, RepOriginId *nodeid)
Definition: commit_ts.c:272
void ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel, ConflictType type, TupleTableSlot *searchslot, TupleTableSlot *remoteslot, List *conflicttuples)
Definition: conflict.c:104
bool GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin, RepOriginId *localorigin, TimestampTz *localts)
Definition: conflict.c:63
ConflictType
Definition: conflict.h:32
@ CT_MULTIPLE_UNIQUE_CONFLICTS
Definition: conflict.h:55
@ CT_INSERT_EXISTS
Definition: conflict.h:34
@ CT_UPDATE_EXISTS
Definition: conflict.h:40
int64 TimestampTz
Definition: timestamp.h:39
int errdetail(const char *fmt,...)
Definition: elog.c:1207
int errhint(const char *fmt,...)
Definition: elog.c:1321
int errcode(int sqlerrcode)
Definition: elog.c:854
int errmsg(const char *fmt,...)
Definition: elog.c:1071
#define LOG
Definition: elog.h:31
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define ereport(elevel,...)
Definition: elog.h:150
bool ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, ItemPointer tupleid, List *arbiterIndexes)
Definition: execIndexing.c:542
List * ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, bool update, bool noDupErr, bool *specConflict, List *arbiterIndexes, bool onlySummarizing)
Definition: execIndexing.c:309
bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate, bool emitError)
Definition: execMain.c:1846
void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
Definition: execMain.c:1970
static void BuildConflictIndexInfo(ResultRelInfo *resultRelInfo, Oid conflictindex)
static bool tuples_equal(TupleTableSlot *slot1, TupleTableSlot *slot2, TypeCacheEntry **eq, Bitmapset *columns)
bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot)
bool RelationFindReplTupleByIndex(Relation rel, Oid idxoid, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot)
void ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo, EState *estate, EPQState *epqstate, TupleTableSlot *searchslot)
void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname)
bool RelationFindDeletedTupleInfoSeq(Relation rel, TupleTableSlot *searchslot, TransactionId oldestxmin, TransactionId *delete_xid, RepOriginId *delete_origin, TimestampTz *delete_time)
static bool should_refetch_tuple(TM_Result res, TM_FailureData *tmfd)
void ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot)
void CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
void ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot)
bool RelationFindDeletedTupleInfoByIndex(Relation rel, Oid idxoid, TupleTableSlot *searchslot, TransactionId oldestxmin, TransactionId *delete_xid, RepOriginId *delete_origin, TimestampTz *delete_time)
static bool FindConflictTuple(ResultRelInfo *resultRelInfo, EState *estate, Oid conflictindex, TupleTableSlot *slot, TupleTableSlot **conflictslot)
static int build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot)
static void CheckAndReportConflict(ResultRelInfo *resultRelInfo, EState *estate, ConflictType type, List *recheckIndexes, TupleTableSlot *searchslot, TupleTableSlot *remoteslot)
static void update_most_recent_deletion_info(TupleTableSlot *scanslot, TransactionId oldestxmin, TransactionId *delete_xid, TimestampTz *delete_time, RepOriginId *delete_origin)
void ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
Definition: execTuples.c:1443
HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree)
Definition: execTuples.c:1833
#define palloc0_object(type)
Definition: fe_memutils.h:75
Datum FunctionCall2Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2)
Definition: fmgr.c:1149
char * format_type_be(Oid type_oid)
Definition: format_type.c:343
Assert(PointerIsAligned(start, uint64))
@ HEAPTUPLE_RECENTLY_DEAD
Definition: heapam.h:128
HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer)
static TransactionId HeapTupleHeaderGetUpdateXid(const HeapTupleHeaderData *tup)
Definition: htup_details.h:397
void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
Definition: index.c:2669
bool index_getnext_slot(IndexScanDesc scan, ScanDirection direction, TupleTableSlot *slot)
Definition: indexam.c:730
IndexScanDesc index_beginscan(Relation heapRelation, Relation indexRelation, Snapshot snapshot, IndexScanInstrumentation *instrument, int nkeys, int norderbys)
Definition: indexam.c:256
void index_close(Relation relation, LOCKMODE lockmode)
Definition: indexam.c:177
void index_endscan(IndexScanDesc scan)
Definition: indexam.c:392
Relation index_open(Oid relationId, LOCKMODE lockmode)
Definition: indexam.c:133
void index_rescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys, int norderbys)
Definition: indexam.c:366
int i
Definition: isn.c:77
static bool ItemPointerIndicatesMovedPartitions(const ItemPointerData *pointer)
Definition: itemptr.h:197
List * lappend(List *list, void *datum)
Definition: list.c:339
void list_free(List *list)
Definition: list.c:1546
bool list_member_oid(const List *list, Oid datum)
Definition: list.c:722
void XactLockTableWait(TransactionId xid, Relation rel, const ItemPointerData *ctid, XLTW_Oper oper)
Definition: lmgr.c:663
@ XLTW_None
Definition: lmgr.h:26
#define NoLock
Definition: lockdefs.h:34
#define RowExclusiveLock
Definition: lockdefs.h:38
@ LockWaitBlock
Definition: lockoptions.h:39
LockTupleMode
Definition: lockoptions.h:50
@ LockTupleShare
Definition: lockoptions.h:54
Oid get_opclass_input_type(Oid opclass)
Definition: lsyscache.c:1331
Oid get_opclass_family(Oid opclass)
Definition: lsyscache.c:1309
RegProcedure get_opcode(Oid opno)
Definition: lsyscache.c:1452
Oid get_opfamily_member(Oid opfamily, Oid lefttype, Oid righttype, int16 strategy)
Definition: lsyscache.c:168
void * palloc0(Size size)
Definition: mcxt.c:1395
void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, CmdType cmdtype)
CmdType
Definition: nodes.h:273
@ CMD_INSERT
Definition: nodes.h:277
@ CMD_DELETE
Definition: nodes.h:278
@ CMD_UPDATE
Definition: nodes.h:276
#define InvalidRepOriginId
Definition: origin.h:33
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:202
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
NameData relname
Definition: pg_class.h:38
#define INDEX_MAX_KEYS
static int list_length(const List *l)
Definition: pg_list.h:152
#define NIL
Definition: pg_list.h:68
#define list_make1_oid(x1)
Definition: pg_list.h:242
#define foreach_oid(var, lst)
Definition: pg_list.h:471
static char * buf
Definition: pg_test_fsync.c:72
#define ERRCODE_T_R_SERIALIZATION_FAILURE
Definition: pgbench.c:77
static bool DatumGetBool(Datum X)
Definition: postgres.h:100
uint64_t Datum
Definition: postgres.h:70
static Pointer DatumGetPointer(Datum X)
Definition: postgres.h:322
unsigned int Oid
Definition: postgres_ext.h:32
#define RelationGetRelid(relation)
Definition: rel.h:514
#define RelationGetDescr(relation)
Definition: rel.h:540
#define RelationGetRelationName(relation)
Definition: rel.h:548
#define IndexRelationGetNumberOfKeyAttributes(relation)
Definition: rel.h:533
void RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
Definition: relcache.c:5794
Bitmapset * RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
Definition: relcache.c:5303
Oid RelationGetReplicaIndex(Relation relation)
Definition: relcache.c:5072
@ INDEX_ATTR_BITMAP_PRIMARY_KEY
Definition: relcache.h:70
@ INDEX_ATTR_BITMAP_IDENTITY_KEY
Definition: relcache.h:71
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
@ ForwardScanDirection
Definition: sdir.h:28
#define SK_SEARCHNULL
Definition: skey.h:121
#define SK_ISNULL
Definition: skey.h:115
Snapshot GetLatestSnapshot(void)
Definition: snapmgr.c:353
void PushActiveSnapshot(Snapshot snapshot)
Definition: snapmgr.c:680
void PopActiveSnapshot(void)
Definition: snapmgr.c:773
Snapshot GetActiveSnapshot(void)
Definition: snapmgr.c:798
#define SnapshotAny
Definition: snapmgr.h:33
#define InitDirtySnapshot(snapshotdata)
Definition: snapmgr.h:42
Oid GetRelationIdentityOrPK(Relation rel)
Definition: relation.c:891
uint16 StrategyNumber
Definition: stratnum.h:22
TimestampTz ts
Definition: conflict.h:78
RepOriginId origin
Definition: conflict.h:77
TransactionId xmin
Definition: conflict.h:75
TupleTableSlot * slot
Definition: conflict.h:71
Snapshot es_snapshot
Definition: execnodes.h:660
Oid fn_oid
Definition: fmgr.h:59
HeapTupleHeader t_data
Definition: htup.h:68
Oid * ii_UniqueOps
Definition: execnodes.h:195
Definition: pg_list.h:54
PublicationActions pubactions
bool gencols_valid_for_update
bool gencols_valid_for_delete
struct HeapTupleData * rd_indextuple
Definition: rel.h:194
TupleDesc rd_att
Definition: rel.h:112
Form_pg_index rd_index
Definition: rel.h:192
Oid * rd_indcollation
Definition: rel.h:217
Form_pg_class rd_rel
Definition: rel.h:111
int ri_NumIndices
Definition: execnodes.h:483
List * ri_onConflictArbiterIndexes
Definition: execnodes.h:580
Relation ri_RelationDesc
Definition: execnodes.h:480
RelationPtr ri_IndexRelationDescs
Definition: execnodes.h:486
TriggerDesc * ri_TrigDesc
Definition: execnodes.h:515
IndexInfo ** ri_IndexRelationInfo
Definition: execnodes.h:489
int sk_flags
Definition: skey.h:66
Oid sk_collation
Definition: skey.h:70
TransactionId xmin
Definition: snapshot.h:153
TransactionId xmax
Definition: snapshot.h:154
ItemPointerData ctid
Definition: tableam.h:149
bool trig_delete_before_row
Definition: reltrigger.h:66
bool trig_update_before_row
Definition: reltrigger.h:61
bool trig_insert_before_row
Definition: reltrigger.h:56
bool has_generated_stored
Definition: tupdesc.h:46
TupleConstr * constr
Definition: tupdesc.h:141
TupleDesc tts_tupleDescriptor
Definition: tuptable.h:123
bool * tts_isnull
Definition: tuptable.h:127
ItemPointerData tts_tid
Definition: tuptable.h:129
Datum * tts_values
Definition: tuptable.h:125
FmgrInfo eq_opr_finfo
Definition: typcache.h:76
Definition: c.h:721
int16 values[FLEXIBLE_ARRAY_MEMBER]
Definition: c.h:728
Definition: c.h:732
Oid values[FLEXIBLE_ARRAY_MEMBER]
Definition: c.h:739
#define FirstLowInvalidHeapAttributeNumber
Definition: sysattr.h:27
Datum SysCacheGetAttrNotNull(int cacheId, HeapTuple tup, AttrNumber attributeNumber)
Definition: syscache.c:625
TupleTableSlot * table_slot_create(Relation relation, List **reglist)
Definition: tableam.c:92
void simple_table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, Snapshot snapshot, TU_UpdateIndexes *update_indexes)
Definition: tableam.c:336
void simple_table_tuple_insert(Relation rel, TupleTableSlot *slot)
Definition: tableam.c:277
void simple_table_tuple_delete(Relation rel, ItemPointer tid, Snapshot snapshot)
Definition: tableam.c:291
TU_UpdateIndexes
Definition: tableam.h:111
@ TU_Summarizing
Definition: tableam.h:119
@ TU_None
Definition: tableam.h:113
static void table_endscan(TableScanDesc scan)
Definition: tableam.h:985
TM_Result
Definition: tableam.h:73
@ TM_Ok
Definition: tableam.h:78
@ TM_Deleted
Definition: tableam.h:93
@ TM_Updated
Definition: tableam.h:90
@ TM_Invisible
Definition: tableam.h:81
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:1549
static void table_rescan(TableScanDesc scan, ScanKeyData *key)
Definition: tableam.h:994
static bool table_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
Definition: tableam.h:1020
static TableScanDesc table_beginscan(Relation rel, Snapshot snapshot, int nkeys, ScanKeyData *key)
Definition: tableam.h:876
#define InvalidTransactionId
Definition: transam.h:31
#define TransactionIdIsValid(xid)
Definition: transam.h:41
bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot *newslot, TM_Result *tmresult, TM_FailureData *tmfd, bool is_merge_update)
Definition: trigger.c:2971
void ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TransitionCaptureState *transition_capture, bool is_crosspart_update)
Definition: trigger.c:2801
bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot)
Definition: trigger.c:2465
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:3144
bool ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot **epqslot, TM_Result *tmresult, TM_FailureData *tmfd, bool is_merge_delete)
Definition: trigger.c:2701
void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot, List *recheckIndexes, TransitionCaptureState *transition_capture)
Definition: trigger.c:2543
bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
Definition: tupdesc.c:590
static FormData_pg_attribute * TupleDescAttr(TupleDesc tupdesc, int i)
Definition: tupdesc.h:160
static void slot_getallattrs(TupleTableSlot *slot)
Definition: tuptable.h:372
static TupleTableSlot * ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
Definition: tuptable.h:525
static void ExecMaterializeSlot(TupleTableSlot *slot)
Definition: tuptable.h:476
TypeCacheEntry * lookup_type_cache(Oid type_id, int flags)
Definition: typcache.c:386
#define TYPECACHE_EQ_OPR_FINFO
Definition: typcache.h:143
const char * type
CommandId GetCurrentCommandId(bool used)
Definition: xact.c:829
uint16 RepOriginId
Definition: xlogdefs.h:68