@ -95,10 +95,13 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
Instrumentation * instr ,
Instrumentation * instr ,
MemoryContext per_tuple_context ) ;
MemoryContext per_tuple_context ) ;
static void AfterTriggerSaveEvent ( EState * estate , ResultRelInfo * relinfo ,
static void AfterTriggerSaveEvent ( EState * estate , ResultRelInfo * relinfo ,
ResultRelInfo * src_partinfo ,
ResultRelInfo * dst_partinfo ,
int event , bool row_trigger ,
int event , bool row_trigger ,
TupleTableSlot * oldtup , TupleTableSlot * newtup ,
TupleTableSlot * oldtup , TupleTableSlot * newtup ,
List * recheckIndexes , Bitmapset * modifiedCols ,
List * recheckIndexes , Bitmapset * modifiedCols ,
TransitionCaptureState * transition_capture ) ;
TransitionCaptureState * transition_capture ,
bool is_crosspart_update ) ;
static void AfterTriggerEnlargeQueryState ( void ) ;
static void AfterTriggerEnlargeQueryState ( void ) ;
static bool before_stmt_triggers_fired ( Oid relid , CmdType cmdType ) ;
static bool before_stmt_triggers_fired ( Oid relid , CmdType cmdType ) ;
@ -2458,8 +2461,10 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
if ( trigdesc & & trigdesc - > trig_insert_after_statement )
if ( trigdesc & & trigdesc - > trig_insert_after_statement )
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_INSERT ,
AfterTriggerSaveEvent ( estate , relinfo , NULL , NULL ,
false , NULL , NULL , NIL , NULL , transition_capture ) ;
TRIGGER_EVENT_INSERT ,
false , NULL , NULL , NIL , NULL , transition_capture ,
false ) ;
}
}
bool
bool
@ -2547,10 +2552,12 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
if ( ( trigdesc & & trigdesc - > trig_insert_after_row ) | |
if ( ( trigdesc & & trigdesc - > trig_insert_after_row ) | |
( transition_capture & & transition_capture - > tcs_insert_new_table ) )
( transition_capture & & transition_capture - > tcs_insert_new_table ) )
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_INSERT ,
AfterTriggerSaveEvent ( estate , relinfo , NULL , NULL ,
TRIGGER_EVENT_INSERT ,
true , NULL , slot ,
true , NULL , slot ,
recheckIndexes , NULL ,
recheckIndexes , NULL ,
transition_capture ) ;
transition_capture ,
false ) ;
}
}
bool
bool
@ -2672,8 +2679,10 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
if ( trigdesc & & trigdesc - > trig_delete_after_statement )
if ( trigdesc & & trigdesc - > trig_delete_after_statement )
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_DELETE ,
AfterTriggerSaveEvent ( estate , relinfo , NULL , NULL ,
false , NULL , NULL , NIL , NULL , transition_capture ) ;
TRIGGER_EVENT_DELETE ,
false , NULL , NULL , NIL , NULL , transition_capture ,
false ) ;
}
}
/*
/*
@ -2768,11 +2777,17 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
return result ;
return result ;
}
}
/*
* Note : is_crosspart_update must be true if the DELETE is being performed
* as part of a cross - partition update .
*/
void
void
ExecARDeleteTriggers ( EState * estate , ResultRelInfo * relinfo ,
ExecARDeleteTriggers ( EState * estate ,
ResultRelInfo * relinfo ,
ItemPointer tupleid ,
ItemPointer tupleid ,
HeapTuple fdw_trigtuple ,
HeapTuple fdw_trigtuple ,
TransitionCaptureState * transition_capture )
TransitionCaptureState * transition_capture ,
bool is_crosspart_update )
{
{
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
@ -2793,9 +2808,11 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
else
else
ExecForceStoreHeapTuple ( fdw_trigtuple , slot , false ) ;
ExecForceStoreHeapTuple ( fdw_trigtuple , slot , false ) ;
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_DELETE ,
AfterTriggerSaveEvent ( estate , relinfo , NULL , NULL ,
TRIGGER_EVENT_DELETE ,
true , slot , NULL , NIL , NULL ,
true , slot , NULL , NIL , NULL ,
transition_capture ) ;
transition_capture ,
is_crosspart_update ) ;
}
}
}
}
@ -2914,10 +2931,12 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
Assert ( relinfo - > ri_RootResultRelInfo = = NULL ) ;
Assert ( relinfo - > ri_RootResultRelInfo = = NULL ) ;
if ( trigdesc & & trigdesc - > trig_update_after_statement )
if ( trigdesc & & trigdesc - > trig_update_after_statement )
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_UPDATE ,
AfterTriggerSaveEvent ( estate , relinfo , NULL , NULL ,
TRIGGER_EVENT_UPDATE ,
false , NULL , NULL , NIL ,
false , NULL , NULL , NIL ,
ExecGetAllUpdatedCols ( relinfo , estate ) ,
ExecGetAllUpdatedCols ( relinfo , estate ) ,
transition_capture ) ;
transition_capture ,
false ) ;
}
}
bool
bool
@ -3052,13 +3071,26 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
return true ;
return true ;
}
}
/*
* Note : ' src_partinfo ' and ' dst_partinfo ' , when non - NULL , refer to the source
* and destination partitions , respectively , of a cross - partition update of
* the root partitioned table mentioned in the query , given by ' relinfo ' .
* ' tupleid ' in that case refers to the ctid of the " old " tuple in the source
* partition , and ' newslot ' contains the " new " tuple in the destination
* partition . This interface allows to support the requirements of
* ExecCrossPartitionUpdateForeignKey ( ) ; is_crosspart_update must be true in
* that case .
*/
void
void
ExecARUpdateTriggers ( EState * estate , ResultRelInfo * relinfo ,
ExecARUpdateTriggers ( EState * estate , ResultRelInfo * relinfo ,
ResultRelInfo * src_partinfo ,
ResultRelInfo * dst_partinfo ,
ItemPointer tupleid ,
ItemPointer tupleid ,
HeapTuple fdw_trigtuple ,
HeapTuple fdw_trigtuple ,
TupleTableSlot * newslot ,
TupleTableSlot * newslot ,
List * recheckIndexes ,
List * recheckIndexes ,
TransitionCaptureState * transition_capture )
TransitionCaptureState * transition_capture ,
bool is_crosspart_update )
{
{
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
@ -3073,12 +3105,19 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
* separately for DELETE and INSERT to capture transition table rows .
* separately for DELETE and INSERT to capture transition table rows .
* In such case , either old tuple or new tuple can be NULL .
* In such case , either old tuple or new tuple can be NULL .
*/
*/
TupleTableSlot * oldslot = ExecGetTriggerOldSlot ( estate , relinfo ) ;
TupleTableSlot * oldslot ;
ResultRelInfo * tupsrc ;
Assert ( ( src_partinfo ! = NULL & & dst_partinfo ! = NULL ) | |
! is_crosspart_update ) ;
tupsrc = src_partinfo ? src_partinfo : relinfo ;
oldslot = ExecGetTriggerOldSlot ( estate , tupsrc ) ;
if ( fdw_trigtuple = = NULL & & ItemPointerIsValid ( tupleid ) )
if ( fdw_trigtuple = = NULL & & ItemPointerIsValid ( tupleid ) )
GetTupleForTrigger ( estate ,
GetTupleForTrigger ( estate ,
NULL ,
NULL ,
relinfo ,
tupsrc ,
tupleid ,
tupleid ,
LockTupleExclusive ,
LockTupleExclusive ,
oldslot ,
oldslot ,
@ -3088,10 +3127,14 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
else
else
ExecClearTuple ( oldslot ) ;
ExecClearTuple ( oldslot ) ;
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_UPDATE ,
AfterTriggerSaveEvent ( estate , relinfo ,
true , oldslot , newslot , recheckIndexes ,
src_partinfo , dst_partinfo ,
TRIGGER_EVENT_UPDATE ,
true ,
oldslot , newslot , recheckIndexes ,
ExecGetAllUpdatedCols ( relinfo , estate ) ,
ExecGetAllUpdatedCols ( relinfo , estate ) ,
transition_capture ) ;
transition_capture ,
is_crosspart_update ) ;
}
}
}
}
@ -3214,8 +3257,11 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
if ( trigdesc & & trigdesc - > trig_truncate_after_statement )
if ( trigdesc & & trigdesc - > trig_truncate_after_statement )
AfterTriggerSaveEvent ( estate , relinfo , TRIGGER_EVENT_TRUNCATE ,
AfterTriggerSaveEvent ( estate , relinfo ,
false , NULL , NULL , NIL , NULL , NULL ) ;
NULL , NULL ,
TRIGGER_EVENT_TRUNCATE ,
false , NULL , NULL , NIL , NULL , NULL ,
false ) ;
}
}
@ -3496,9 +3542,9 @@ typedef SetConstraintStateData *SetConstraintState;
* Per - trigger - event data
* Per - trigger - event data
*
*
* The actual per - event data , AfterTriggerEventData , includes DONE / IN_PROGRESS
* The actual per - event data , AfterTriggerEventData , includes DONE / IN_PROGRESS
* status bits and up to two tuple CTIDs . Each event record also has an
* status bits , up to two tuple CTIDs , and optionally two OIDs of partitions .
* associated AfterTriggerSharedData that is shared across all instances of
* Each event record also has an associated AfterTriggerSharedData that is
* similar events within a " chunk " .
* shared across all instances of s imilar events within a " chunk " .
*
*
* For row - level triggers , we arrange not to waste storage on unneeded ctid
* For row - level triggers , we arrange not to waste storage on unneeded ctid
* fields . Updates of regular tables use two ; inserts and deletes of regular
* fields . Updates of regular tables use two ; inserts and deletes of regular
@ -3509,6 +3555,11 @@ typedef SetConstraintStateData *SetConstraintState;
* tuple ( s ) . This permits storing tuples once regardless of the number of
* tuple ( s ) . This permits storing tuples once regardless of the number of
* row - level triggers on a foreign table .
* row - level triggers on a foreign table .
*
*
* When updates on partitioned tables cause rows to move between partitions ,
* the OIDs of both partitions are stored too , so that the tuples can be
* fetched ; such entries are marked AFTER_TRIGGER_CP_UPDATE ( for " cross-
* partition update " ).
*
* Note that we need triggers on foreign tables to be fired in exactly the
* Note that we need triggers on foreign tables to be fired in exactly the
* order they were queued , so that the tuples come out of the tuplestore in
* order they were queued , so that the tuples come out of the tuplestore in
* the right order . To ensure that , we forbid deferrable ( constraint )
* the right order . To ensure that , we forbid deferrable ( constraint )
@ -3531,16 +3582,16 @@ typedef SetConstraintStateData *SetConstraintState;
*/
*/
typedef uint32 TriggerFlags ;
typedef uint32 TriggerFlags ;
# define AFTER_TRIGGER_OFFSET 0x0F FFFFFF /* must be low-order bits */
# define AFTER_TRIGGER_OFFSET 0x07 FFFFFF /* must be low-order bits */
# define AFTER_TRIGGER_DONE 0x1 0000000
# define AFTER_TRIGGER_DONE 0x8 0000000
# define AFTER_TRIGGER_IN_PROGRESS 0x2 0000000
# define AFTER_TRIGGER_IN_PROGRESS 0x4 0000000
/* bits describing the size and tuple sources of this event */
/* bits describing the size and tuple sources of this event */
# define AFTER_TRIGGER_FDW_REUSE 0x00000000
# define AFTER_TRIGGER_FDW_REUSE 0x00000000
# define AFTER_TRIGGER_FDW_FETCH 0x8 0000000
# define AFTER_TRIGGER_FDW_FETCH 0x2 0000000
# define AFTER_TRIGGER_1CTID 0x4 0000000
# define AFTER_TRIGGER_1CTID 0x1 0000000
# define AFTER_TRIGGER_2CTID 0xC 0000000
# define AFTER_TRIGGER_2CTID 0x3 0000000
# define AFTER_TRIGGER_TUP_BITS 0xC0 000000
# define AFTER_TRIGGER_CP_UPDATE 0x08 000000
# define AFTER_TRIGGER_TUP_BITS 0x38000000
typedef struct AfterTriggerSharedData * AfterTriggerShared ;
typedef struct AfterTriggerSharedData * AfterTriggerShared ;
typedef struct AfterTriggerSharedData
typedef struct AfterTriggerSharedData
@ -3560,27 +3611,45 @@ typedef struct AfterTriggerEventData
TriggerFlags ate_flags ; /* status bits and offset to shared data */
TriggerFlags ate_flags ; /* status bits and offset to shared data */
ItemPointerData ate_ctid1 ; /* inserted, deleted, or old updated tuple */
ItemPointerData ate_ctid1 ; /* inserted, deleted, or old updated tuple */
ItemPointerData ate_ctid2 ; /* new updated tuple */
ItemPointerData ate_ctid2 ; /* new updated tuple */
/*
* During a cross - partition update of a partitioned table , we also store
* the OIDs of source and destination partitions that are needed to fetch
* the old ( ctid1 ) and the new tuple ( ctid2 ) from , respectively .
*/
Oid ate_src_part ;
Oid ate_dst_part ;
} AfterTriggerEventData ;
} AfterTriggerEventData ;
/* AfterTriggerEventData, minus ate_ctid2 */
/* AfterTriggerEventData, minus ate_src_part, ate_dst_part */
typedef struct AfterTriggerEventDataNoOids
{
TriggerFlags ate_flags ;
ItemPointerData ate_ctid1 ;
ItemPointerData ate_ctid2 ;
} AfterTriggerEventDataNoOids ;
/* AfterTriggerEventData, minus ate_*_part and ate_ctid2 */
typedef struct AfterTriggerEventDataOneCtid
typedef struct AfterTriggerEventDataOneCtid
{
{
TriggerFlags ate_flags ; /* status bits and offset to shared data */
TriggerFlags ate_flags ; /* status bits and offset to shared data */
ItemPointerData ate_ctid1 ; /* inserted, deleted, or old updated tuple */
ItemPointerData ate_ctid1 ; /* inserted, deleted, or old updated tuple */
} AfterTriggerEventDataOneCtid ;
} AfterTriggerEventDataOneCtid ;
/* AfterTriggerEventData, minus ate_ctid1 and ate_ctid2 */
/* AfterTriggerEventData, minus ate_*_part, ate_ ctid1 and ate_ctid2 */
typedef struct AfterTriggerEventDataZeroCtids
typedef struct AfterTriggerEventDataZeroCtids
{
{
TriggerFlags ate_flags ; /* status bits and offset to shared data */
TriggerFlags ate_flags ; /* status bits and offset to shared data */
} AfterTriggerEventDataZeroCtids ;
} AfterTriggerEventDataZeroCtids ;
# define SizeofTriggerEvent(evt) \
# define SizeofTriggerEvent(evt) \
( ( ( evt ) - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_2CTID ? \
( ( ( evt ) - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_CP_UPDATE ? \
sizeof ( AfterTriggerEventData ) : \
sizeof ( AfterTriggerEventData ) : \
( ( evt ) - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_1CTID ? \
( ( ( evt ) - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_2CTID ? \
sizeof ( AfterTriggerEventDataOneCtid ) : \
sizeof ( AfterTriggerEventDataNoOids ) : \
sizeof ( AfterTriggerEventDataZeroCtids ) )
( ( ( evt ) - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_1CTID ? \
sizeof ( AfterTriggerEventDataOneCtid ) : \
sizeof ( AfterTriggerEventDataZeroCtids ) ) ) )
# define GetTriggerSharedData(evt) \
# define GetTriggerSharedData(evt) \
( ( AfterTriggerShared ) ( ( char * ) ( evt ) + ( ( evt ) - > ate_flags & AFTER_TRIGGER_OFFSET ) ) )
( ( AfterTriggerShared ) ( ( char * ) ( evt ) + ( ( evt ) - > ate_flags & AFTER_TRIGGER_OFFSET ) ) )
@ -3762,6 +3831,8 @@ static AfterTriggersData afterTriggers;
static void AfterTriggerExecute ( EState * estate ,
static void AfterTriggerExecute ( EState * estate ,
AfterTriggerEvent event ,
AfterTriggerEvent event ,
ResultRelInfo * relInfo ,
ResultRelInfo * relInfo ,
ResultRelInfo * src_relInfo ,
ResultRelInfo * dst_relInfo ,
TriggerDesc * trigdesc ,
TriggerDesc * trigdesc ,
FmgrInfo * finfo ,
FmgrInfo * finfo ,
Instrumentation * instr ,
Instrumentation * instr ,
@ -4096,8 +4167,16 @@ afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs)
* fmgr lookup cache space at the caller level . ( For triggers fired at
* fmgr lookup cache space at the caller level . ( For triggers fired at
* the end of a query , we can even piggyback on the executor ' s state . )
* the end of a query , we can even piggyback on the executor ' s state . )
*
*
* When fired for a cross - partition update of a partitioned table , the old
* tuple is fetched using ' src_relInfo ' ( the source leaf partition ) and
* the new tuple using ' dst_relInfo ' ( the destination leaf partition ) , though
* both are converted into the root partitioned table ' s format before passing
* to the trigger function .
*
* event : event currently being fired .
* event : event currently being fired .
* rel : open relation for event .
* relInfo : result relation for event .
* src_relInfo : source partition of a cross - partition update
* dst_relInfo : its destination partition
* trigdesc : working copy of rel ' s trigger info .
* trigdesc : working copy of rel ' s trigger info .
* finfo : array of fmgr lookup cache entries ( one per trigger in trigdesc ) .
* finfo : array of fmgr lookup cache entries ( one per trigger in trigdesc ) .
* instr : array of EXPLAIN ANALYZE instrumentation nodes ( one per trigger ) ,
* instr : array of EXPLAIN ANALYZE instrumentation nodes ( one per trigger ) ,
@ -4111,6 +4190,8 @@ static void
AfterTriggerExecute ( EState * estate ,
AfterTriggerExecute ( EState * estate ,
AfterTriggerEvent event ,
AfterTriggerEvent event ,
ResultRelInfo * relInfo ,
ResultRelInfo * relInfo ,
ResultRelInfo * src_relInfo ,
ResultRelInfo * dst_relInfo ,
TriggerDesc * trigdesc ,
TriggerDesc * trigdesc ,
FmgrInfo * finfo , Instrumentation * instr ,
FmgrInfo * finfo , Instrumentation * instr ,
MemoryContext per_tuple_context ,
MemoryContext per_tuple_context ,
@ -4118,6 +4199,8 @@ AfterTriggerExecute(EState *estate,
TupleTableSlot * trig_tuple_slot2 )
TupleTableSlot * trig_tuple_slot2 )
{
{
Relation rel = relInfo - > ri_RelationDesc ;
Relation rel = relInfo - > ri_RelationDesc ;
Relation src_rel = src_relInfo - > ri_RelationDesc ;
Relation dst_rel = dst_relInfo - > ri_RelationDesc ;
AfterTriggerShared evtshared = GetTriggerSharedData ( event ) ;
AfterTriggerShared evtshared = GetTriggerSharedData ( event ) ;
Oid tgoid = evtshared - > ats_tgoid ;
Oid tgoid = evtshared - > ats_tgoid ;
TriggerData LocTriggerData = { 0 } ;
TriggerData LocTriggerData = { 0 } ;
@ -4198,12 +4281,35 @@ AfterTriggerExecute(EState *estate,
default :
default :
if ( ItemPointerIsValid ( & ( event - > ate_ctid1 ) ) )
if ( ItemPointerIsValid ( & ( event - > ate_ctid1 ) ) )
{
{
LocTriggerData . tg_trigslot = ExecGetTriggerOldSlot ( estate , relInfo ) ;
TupleTableSlot * src_slot = ExecGetTriggerOldSlot ( estate ,
src_relInfo ) ;
if ( ! table_tuple_fetch_row_version ( rel , & ( event - > ate_ctid1 ) ,
if ( ! table_tuple_fetch_row_version ( src_rel ,
& ( event - > ate_ctid1 ) ,
SnapshotAny ,
SnapshotAny ,
LocTriggerData . tg_trig slot) )
src_ slot) )
elog ( ERROR , " failed to fetch tuple1 for AFTER trigger " ) ;
elog ( ERROR , " failed to fetch tuple1 for AFTER trigger " ) ;
/*
* Store the tuple fetched from the source partition into the
* target ( root partitioned ) table slot , converting if needed .
*/
if ( src_relInfo ! = relInfo )
{
TupleConversionMap * map = ExecGetChildToRootMap ( src_relInfo ) ;
LocTriggerData . tg_trigslot = ExecGetTriggerOldSlot ( estate , relInfo ) ;
if ( map )
{
execute_attr_map_slot ( map - > attrMap ,
src_slot ,
LocTriggerData . tg_trigslot ) ;
}
else
ExecCopySlot ( LocTriggerData . tg_trigslot , src_slot ) ;
}
else
LocTriggerData . tg_trigslot = src_slot ;
LocTriggerData . tg_trigtuple =
LocTriggerData . tg_trigtuple =
ExecFetchSlotHeapTuple ( LocTriggerData . tg_trigslot , false , & should_free_trig ) ;
ExecFetchSlotHeapTuple ( LocTriggerData . tg_trigslot , false , & should_free_trig ) ;
}
}
@ -4213,16 +4319,40 @@ AfterTriggerExecute(EState *estate,
}
}
/* don't touch ctid2 if not there */
/* don't touch ctid2 if not there */
if ( ( event - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = =
if ( ( ( event - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = = AFTER_TRIGGER_2CTID | |
AFTER_TRIGGER_2CTID & &
( event - > ate_flags & AFTER_TRIGGER_CP_UPDATE ) ) & &
ItemPointerIsValid ( & ( event - > ate_ctid2 ) ) )
ItemPointerIsValid ( & ( event - > ate_ctid2 ) ) )
{
{
LocTriggerData . tg_newslot = ExecGetTriggerNewSlot ( estate , relInfo ) ;
TupleTableSlot * dst_slot = ExecGetTriggerNewSlot ( estate ,
dst_relInfo ) ;
if ( ! table_tuple_fetch_row_version ( rel , & ( event - > ate_ctid2 ) ,
if ( ! table_tuple_fetch_row_version ( dst_rel ,
& ( event - > ate_ctid2 ) ,
SnapshotAny ,
SnapshotAny ,
LocTriggerData . tg_new slot) )
dst_ slot) )
elog ( ERROR , " failed to fetch tuple2 for AFTER trigger " ) ;
elog ( ERROR , " failed to fetch tuple2 for AFTER trigger " ) ;
/*
* Store the tuple fetched from the destination partition into
* the target ( root partitioned ) table slot , converting if
* needed .
*/
if ( dst_relInfo ! = relInfo )
{
TupleConversionMap * map = ExecGetChildToRootMap ( dst_relInfo ) ;
LocTriggerData . tg_newslot = ExecGetTriggerNewSlot ( estate , relInfo ) ;
if ( map )
{
execute_attr_map_slot ( map - > attrMap ,
dst_slot ,
LocTriggerData . tg_newslot ) ;
}
else
ExecCopySlot ( LocTriggerData . tg_newslot , dst_slot ) ;
}
else
LocTriggerData . tg_newslot = dst_slot ;
LocTriggerData . tg_newtuple =
LocTriggerData . tg_newtuple =
ExecFetchSlotHeapTuple ( LocTriggerData . tg_newslot , false , & should_free_new ) ;
ExecFetchSlotHeapTuple ( LocTriggerData . tg_newslot , false , & should_free_new ) ;
}
}
@ -4451,13 +4581,17 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
if ( ( event - > ate_flags & AFTER_TRIGGER_IN_PROGRESS ) & &
if ( ( event - > ate_flags & AFTER_TRIGGER_IN_PROGRESS ) & &
evtshared - > ats_firing_id = = firing_id )
evtshared - > ats_firing_id = = firing_id )
{
{
ResultRelInfo * src_rInfo ,
* dst_rInfo ;
/*
/*
* So let ' s fire it . . . but first , find the correct relation if
* So let ' s fire it . . . but first , find the correct relation if
* this is not the same relation as before .
* this is not the same relation as before .
*/
*/
if ( rel = = NULL | | RelationGetRelid ( rel ) ! = evtshared - > ats_relid )
if ( rel = = NULL | | RelationGetRelid ( rel ) ! = evtshared - > ats_relid )
{
{
rInfo = ExecGetTriggerResultRel ( estate , evtshared - > ats_relid ) ;
rInfo = ExecGetTriggerResultRel ( estate , evtshared - > ats_relid ,
NULL ) ;
rel = rInfo - > ri_RelationDesc ;
rel = rInfo - > ri_RelationDesc ;
/* Catch calls with insufficient relcache refcounting */
/* Catch calls with insufficient relcache refcounting */
Assert ( ! RelationHasReferenceCountZero ( rel ) ) ;
Assert ( ! RelationHasReferenceCountZero ( rel ) ) ;
@ -4482,12 +4616,33 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
evtshared - > ats_relid ) ;
evtshared - > ats_relid ) ;
}
}
/*
* Look up source and destination partition result rels of a
* cross - partition update event .
*/
if ( ( event - > ate_flags & AFTER_TRIGGER_TUP_BITS ) = =
AFTER_TRIGGER_CP_UPDATE )
{
Assert ( OidIsValid ( event - > ate_src_part ) & &
OidIsValid ( event - > ate_dst_part ) ) ;
src_rInfo = ExecGetTriggerResultRel ( estate ,
event - > ate_src_part ,
rInfo ) ;
dst_rInfo = ExecGetTriggerResultRel ( estate ,
event - > ate_dst_part ,
rInfo ) ;
}
else
src_rInfo = dst_rInfo = rInfo ;
/*
/*
* Fire it . Note that the AFTER_TRIGGER_IN_PROGRESS flag is
* Fire it . Note that the AFTER_TRIGGER_IN_PROGRESS flag is
* still set , so recursive examinations of the event list
* still set , so recursive examinations of the event list
* won ' t try to re - fire it .
* won ' t try to re - fire it .
*/
*/
AfterTriggerExecute ( estate , event , rInfo , trigdesc , finfo , instr ,
AfterTriggerExecute ( estate , event , rInfo ,
src_rInfo , dst_rInfo ,
trigdesc , finfo , instr ,
per_tuple_context , slot1 , slot2 ) ;
per_tuple_context , slot1 , slot2 ) ;
/*
/*
@ -5767,14 +5922,35 @@ AfterTriggerPendingOnRel(Oid relid)
* Transition tuplestores are built now , rather than when events are pulled
* Transition tuplestores are built now , rather than when events are pulled
* off of the queue because AFTER ROW triggers are allowed to select from the
* off of the queue because AFTER ROW triggers are allowed to select from the
* transition tables for the statement .
* transition tables for the statement .
*
* This contains special support to queue the update events for the case where
* a partitioned table undergoing a cross - partition update may have foreign
* keys pointing into it . Normally , a partitioned table ' s row triggers are
* not fired because the leaf partition ( s ) which are modified as a result of
* the operation on the partitioned table contain the same triggers which are
* fired instead . But that general scheme can cause problematic behavior with
* foreign key triggers during cross - partition updates , which are implemented
* as DELETE on the source partition followed by INSERT into the destination
* partition . Specifically , firing DELETE triggers would lead to the wrong
* foreign key action to be enforced considering that the original command is
* UPDATE ; in this case , this function is called with relinfo as the
* partitioned table , and src_partinfo and dst_partinfo referring to the
* source and target leaf partitions , respectively .
*
* is_crosspart_update is true either when a DELETE event is fired on the
* source partition ( which is to be ignored ) or an UPDATE event is fired on
* the root partitioned table .
* - - - - - - - - - -
* - - - - - - - - - -
*/
*/
static void
static void
AfterTriggerSaveEvent ( EState * estate , ResultRelInfo * relinfo ,
AfterTriggerSaveEvent ( EState * estate , ResultRelInfo * relinfo ,
ResultRelInfo * src_partinfo ,
ResultRelInfo * dst_partinfo ,
int event , bool row_trigger ,
int event , bool row_trigger ,
TupleTableSlot * oldslot , TupleTableSlot * newslot ,
TupleTableSlot * oldslot , TupleTableSlot * newslot ,
List * recheckIndexes , Bitmapset * modifiedCols ,
List * recheckIndexes , Bitmapset * modifiedCols ,
TransitionCaptureState * transition_capture )
TransitionCaptureState * transition_capture ,
bool is_crosspart_update )
{
{
Relation rel = relinfo - > ri_RelationDesc ;
Relation rel = relinfo - > ri_RelationDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
TriggerDesc * trigdesc = relinfo - > ri_TrigDesc ;
@ -5854,6 +6030,19 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
return ;
return ;
}
}
/*
* We normally don ' t see partitioned tables here for row level triggers
* except in the special case of a cross - partition update . In that case ,
* nodeModifyTable . c : ExecCrossPartitionUpdateForeignKey ( ) calls here to
* queue an update event on the root target partitioned table , also
* passing the source and destination partitions and their tuples .
*/
Assert ( ! row_trigger | |
rel - > rd_rel - > relkind ! = RELKIND_PARTITIONED_TABLE | |
( is_crosspart_update & &
TRIGGER_FIRED_BY_UPDATE ( event ) & &
src_partinfo ! = NULL & & dst_partinfo ! = NULL ) ) ;
/*
/*
* Validate the event code and collect the associated tuple CTIDs .
* Validate the event code and collect the associated tuple CTIDs .
*
*
@ -5914,6 +6103,19 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
Assert ( newslot ! = NULL ) ;
Assert ( newslot ! = NULL ) ;
ItemPointerCopy ( & ( oldslot - > tts_tid ) , & ( new_event . ate_ctid1 ) ) ;
ItemPointerCopy ( & ( oldslot - > tts_tid ) , & ( new_event . ate_ctid1 ) ) ;
ItemPointerCopy ( & ( newslot - > tts_tid ) , & ( new_event . ate_ctid2 ) ) ;
ItemPointerCopy ( & ( newslot - > tts_tid ) , & ( new_event . ate_ctid2 ) ) ;
/*
* Also remember the OIDs of partitions to fetch these tuples
* out of later in AfterTriggerExecute ( ) .
*/
if ( rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE )
{
Assert ( src_partinfo ! = NULL & & dst_partinfo ! = NULL ) ;
new_event . ate_src_part =
RelationGetRelid ( src_partinfo - > ri_RelationDesc ) ;
new_event . ate_dst_part =
RelationGetRelid ( dst_partinfo - > ri_RelationDesc ) ;
}
}
}
else
else
{
{
@ -5938,13 +6140,53 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
break ;
break ;
}
}
/* Determine flags */
if ( ! ( relkind = = RELKIND_FOREIGN_TABLE & & row_trigger ) )
if ( ! ( relkind = = RELKIND_FOREIGN_TABLE & & row_trigger ) )
new_event . ate_flags = ( row_trigger & & event = = TRIGGER_EVENT_UPDATE ) ?
{
AFTER_TRIGGER_2CTID : AFTER_TRIGGER_1CTID ;
if ( row_trigger & & event = = TRIGGER_EVENT_UPDATE )
{
if ( relkind = = RELKIND_PARTITIONED_TABLE )
new_event . ate_flags = AFTER_TRIGGER_CP_UPDATE ;
else
new_event . ate_flags = AFTER_TRIGGER_2CTID ;
}
else
new_event . ate_flags = AFTER_TRIGGER_1CTID ;
}
/* else, we'll initialize ate_flags for each trigger */
/* else, we'll initialize ate_flags for each trigger */
tgtype_level = ( row_trigger ? TRIGGER_TYPE_ROW : TRIGGER_TYPE_STATEMENT ) ;
tgtype_level = ( row_trigger ? TRIGGER_TYPE_ROW : TRIGGER_TYPE_STATEMENT ) ;
/*
* Must convert / copy the source and destination partition tuples into the
* root partitioned table ' s format / slot , because the processing in the
* loop below expects both oldslot and newslot tuples to be in that form .
*/
if ( row_trigger & & rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE )
{
TupleTableSlot * rootslot ;
TupleConversionMap * map ;
rootslot = ExecGetTriggerOldSlot ( estate , relinfo ) ;
map = ExecGetChildToRootMap ( src_partinfo ) ;
if ( map )
oldslot = execute_attr_map_slot ( map - > attrMap ,
oldslot ,
rootslot ) ;
else
oldslot = ExecCopySlot ( rootslot , oldslot ) ;
rootslot = ExecGetTriggerNewSlot ( estate , relinfo ) ;
map = ExecGetChildToRootMap ( dst_partinfo ) ;
if ( map )
newslot = execute_attr_map_slot ( map - > attrMap ,
newslot ,
rootslot ) ;
else
newslot = ExecCopySlot ( rootslot , newslot ) ;
}
for ( i = 0 ; i < trigdesc - > numtriggers ; i + + )
for ( i = 0 ; i < trigdesc - > numtriggers ; i + + )
{
{
Trigger * trigger = & trigdesc - > triggers [ i ] ;
Trigger * trigger = & trigdesc - > triggers [ i ] ;
@ -5973,13 +6215,30 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
/*
/*
* If the trigger is a foreign key enforcement trigger , there are
* If the trigger is a foreign key enforcement trigger , there are
* certain cases where we can skip queueing the event because we can
* certain cases where we can skip queueing the event because we can
* tell by inspection that the FK constraint will still pass .
* tell by inspection that the FK constraint will still pass . There
* are also some cases during cross - partition updates of a partitioned
* table where queuing the event can be skipped .
*/
*/
if ( TRIGGER_FIRED_BY_UPDATE ( event ) | | TRIGGER_FIRED_BY_DELETE ( event ) )
if ( TRIGGER_FIRED_BY_UPDATE ( event ) | | TRIGGER_FIRED_BY_DELETE ( event ) )
{
{
switch ( RI_FKey_trigger_type ( trigger - > tgfoid ) )
switch ( RI_FKey_trigger_type ( trigger - > tgfoid ) )
{
{
case RI_TRIGGER_PK :
case RI_TRIGGER_PK :
/*
* For cross - partitioned updates of partitioned PK table ,
* skip the event fired by the component delete on the
* source leaf partition unless the constraint originates
* in the partition itself ( ! tgisclone ) , because the
* update event that will be fired on the root
* ( partitioned ) target table will be used to perform the
* necessary foreign key enforcement action .
*/
if ( is_crosspart_update & &
TRIGGER_FIRED_BY_DELETE ( event ) & &
trigger - > tgisclone )
continue ;
/* Update or delete on trigger's PK table */
/* Update or delete on trigger's PK table */
if ( ! RI_FKey_pk_upd_check_required ( trigger , rel ,
if ( ! RI_FKey_pk_upd_check_required ( trigger , rel ,
oldslot , newslot ) )
oldslot , newslot ) )
@ -5990,8 +6249,20 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
break ;
break ;
case RI_TRIGGER_FK :
case RI_TRIGGER_FK :
/* Update on trigger's FK table */
if ( ! RI_FKey_fk_upd_check_required ( trigger , rel ,
/*
* Update on trigger ' s FK table . We can skip the update
* event fired on a partitioned table during a
* cross - partition of that table , because the insert event
* that is fired on the destination leaf partition would
* suffice to perform the necessary foreign key check .
* Moreover , RI_FKey_fk_upd_check_required ( ) expects to be
* passed a tuple that contains system attributes , most of
* which are not present in the virtual slot belonging to
* a partitioned table .
*/
if ( rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE | |
! RI_FKey_fk_upd_check_required ( trigger , rel ,
oldslot , newslot ) )
oldslot , newslot ) )
{
{
/* skip queuing this event */
/* skip queuing this event */
@ -6000,7 +6271,18 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
break ;
break ;
case RI_TRIGGER_NONE :
case RI_TRIGGER_NONE :
/* Not an FK trigger */
/*
* Not an FK trigger . No need to queue the update event
* fired during a cross - partitioned update of a
* partitioned table , because the same row trigger must be
* present in the leaf partition ( s ) that are affected as
* part of this update and the events fired on them are
* queued instead .
*/
if ( row_trigger & &
rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE )
continue ;
break ;
break ;
}
}
}
}