@@ -2854,8 +2854,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
28542854 {
28552855 HeapTuple trigtuple ;
28562856
2857- Assert (HeapTupleIsValid (fdw_trigtuple ) ^ ItemPointerIsValid (tupleid ));
2858- if (fdw_trigtuple == NULL )
2857+ /*
2858+ * Note: if the UPDATE is converted into a DELETE+INSERT as part of
2859+ * update-partition-key operation, then this function is also called
2860+ * separately for DELETE and INSERT to capture transition table rows.
2861+ * In such case, either old tuple or new tuple can be NULL.
2862+ */
2863+ if (fdw_trigtuple == NULL && ItemPointerIsValid (tupleid ))
28592864 trigtuple = GetTupleForTrigger (estate ,
28602865 NULL ,
28612866 relinfo ,
@@ -5414,7 +5419,12 @@ AfterTriggerPendingOnRel(Oid relid)
54145419 * triggers actually need to be queued. It is also called after each row,
54155420 * even if there are no triggers for that event, if there are any AFTER
54165421 * STATEMENT triggers for the statement which use transition tables, so that
5417- * the transition tuplestores can be built.
5422+ * the transition tuplestores can be built. Furthermore, if the transition
5423+ * capture is happening for UPDATEd rows being moved to another partition due
5424+ * to the partition-key being changed, then this function is called once when
5425+ * the row is deleted (to capture OLD row), and once when the row is inserted
5426+ * into another partition (to capture NEW row). This is done separately because
5427+ * DELETE and INSERT happen on different tables.
54185428 *
54195429 * Transition tuplestores are built now, rather than when events are pulled
54205430 * off of the queue because AFTER ROW triggers are allowed to select from the
@@ -5463,12 +5473,25 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
54635473 bool update_new_table = transition_capture -> tcs_update_new_table ;
54645474 bool insert_new_table = transition_capture -> tcs_insert_new_table ;;
54655475
5466- if ((event == TRIGGER_EVENT_DELETE && delete_old_table ) ||
5467- (event == TRIGGER_EVENT_UPDATE && update_old_table ))
5476+ /*
5477+ * For INSERT events newtup should be non-NULL, for DELETE events
5478+ * oldtup should be non-NULL, whereas for UPDATE events normally both
5479+ * oldtup and newtup are non-NULL. But for UPDATE events fired for
5480+ * capturing transition tuples during UPDATE partition-key row
5481+ * movement, oldtup is NULL when the event is for a row being inserted,
5482+ * whereas newtup is NULL when the event is for a row being deleted.
5483+ */
5484+ Assert (!(event == TRIGGER_EVENT_DELETE && delete_old_table &&
5485+ oldtup == NULL ));
5486+ Assert (!(event == TRIGGER_EVENT_INSERT && insert_new_table &&
5487+ newtup == NULL ));
5488+
5489+ if (oldtup != NULL &&
5490+ ((event == TRIGGER_EVENT_DELETE && delete_old_table ) ||
5491+ (event == TRIGGER_EVENT_UPDATE && update_old_table )))
54685492 {
54695493 Tuplestorestate * old_tuplestore ;
54705494
5471- Assert (oldtup != NULL );
54725495 old_tuplestore = transition_capture -> tcs_private -> old_tuplestore ;
54735496
54745497 if (map != NULL )
@@ -5481,12 +5504,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
54815504 else
54825505 tuplestore_puttuple (old_tuplestore , oldtup );
54835506 }
5484- if ((event == TRIGGER_EVENT_INSERT && insert_new_table ) ||
5485- (event == TRIGGER_EVENT_UPDATE && update_new_table ))
5507+ if (newtup != NULL &&
5508+ ((event == TRIGGER_EVENT_INSERT && insert_new_table ) ||
5509+ (event == TRIGGER_EVENT_UPDATE && update_new_table )))
54865510 {
54875511 Tuplestorestate * new_tuplestore ;
54885512
5489- Assert (newtup != NULL );
54905513 new_tuplestore = transition_capture -> tcs_private -> new_tuplestore ;
54915514
54925515 if (original_insert_tuple != NULL )
@@ -5502,11 +5525,18 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
55025525 tuplestore_puttuple (new_tuplestore , newtup );
55035526 }
55045527
5505- /* If transition tables are the only reason we're here, return. */
5528+ /*
5529+ * If transition tables are the only reason we're here, return. As
5530+ * mentioned above, we can also be here during update tuple routing in
5531+ * presence of transition tables, in which case this function is called
5532+ * separately for oldtup and newtup, so we expect exactly one of them
5533+ * to be NULL.
5534+ */
55065535 if (trigdesc == NULL ||
55075536 (event == TRIGGER_EVENT_DELETE && !trigdesc -> trig_delete_after_row ) ||
55085537 (event == TRIGGER_EVENT_INSERT && !trigdesc -> trig_insert_after_row ) ||
5509- (event == TRIGGER_EVENT_UPDATE && !trigdesc -> trig_update_after_row ))
5538+ (event == TRIGGER_EVENT_UPDATE && !trigdesc -> trig_update_after_row ) ||
5539+ (event == TRIGGER_EVENT_UPDATE && ((oldtup == NULL ) ^ (newtup == NULL ))))
55105540 return ;
55115541 }
55125542
0 commit comments