@@ -2854,8 +2854,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
2854
2854
{
2855
2855
HeapTuple trigtuple ;
2856
2856
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 ))
2859
2864
trigtuple = GetTupleForTrigger (estate ,
2860
2865
NULL ,
2861
2866
relinfo ,
@@ -5414,7 +5419,12 @@ AfterTriggerPendingOnRel(Oid relid)
5414
5419
* triggers actually need to be queued. It is also called after each row,
5415
5420
* even if there are no triggers for that event, if there are any AFTER
5416
5421
* 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.
5418
5428
*
5419
5429
* Transition tuplestores are built now, rather than when events are pulled
5420
5430
* off of the queue because AFTER ROW triggers are allowed to select from the
@@ -5463,12 +5473,25 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
5463
5473
bool update_new_table = transition_capture -> tcs_update_new_table ;
5464
5474
bool insert_new_table = transition_capture -> tcs_insert_new_table ;;
5465
5475
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 )))
5468
5492
{
5469
5493
Tuplestorestate * old_tuplestore ;
5470
5494
5471
- Assert (oldtup != NULL );
5472
5495
old_tuplestore = transition_capture -> tcs_private -> old_tuplestore ;
5473
5496
5474
5497
if (map != NULL )
@@ -5481,12 +5504,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
5481
5504
else
5482
5505
tuplestore_puttuple (old_tuplestore , oldtup );
5483
5506
}
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 )))
5486
5510
{
5487
5511
Tuplestorestate * new_tuplestore ;
5488
5512
5489
- Assert (newtup != NULL );
5490
5513
new_tuplestore = transition_capture -> tcs_private -> new_tuplestore ;
5491
5514
5492
5515
if (original_insert_tuple != NULL )
@@ -5502,11 +5525,18 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
5502
5525
tuplestore_puttuple (new_tuplestore , newtup );
5503
5526
}
5504
5527
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
+ */
5506
5535
if (trigdesc == NULL ||
5507
5536
(event == TRIGGER_EVENT_DELETE && !trigdesc -> trig_delete_after_row ) ||
5508
5537
(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 ))))
5510
5540
return ;
5511
5541
}
5512
5542
0 commit comments